将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?

Posted

技术标签:

【中文标题】将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?【英文标题】:Does casting `std::floor()` and `std::ceil()` to integer type always give the correct result?将 `std::floor()` 和 `std::ceil()` 转换为整数类型是否总是给出正确的结果? 【发布时间】:2016-05-02 00:30:42 【问题描述】:

我很怀疑这些函数之一可能会给出这样的错误结果:

std::floor(2000.0 / 1000.0) --> std::floor(1.999999999999) --> 1
or
std::ceil(18 / 3) --> std::ceil(6.000000000001) --> 7

这样的事情会发生吗?如果确实存在这样的风险,我打算使用下面的功能来安全地工作。但是,这真的有必要吗?

constexpr long double EPSILON = 1e-10;

intmax_t GuaranteedFloor(const long double & Number)

    if (Number > 0)
    
        return static_cast<intmax_t>(std::floor(Number) + EPSILON);
    
    else
    
        return static_cast<intmax_t>(std::floor(Number) - EPSILON);
    


intmax_t GuaranteedCeil(const long double & Number)

    if (Number > 0)
    
        return static_cast<intmax_t>(std::ceil(Number) + EPSILON);
    
    else
    
        return static_cast<intmax_t>(std::ceil(Number) - EPSILON);
    

(注意:我假设给定的“long double”参数将适合“intmax_t”返回类型。)

【问题讨论】:

我认为这些例子是安全的(整数,在浮点精度范围内),但是,例如,“3.3 / 1.1”可能会给出“不精确的 3”。 EPSILON 不会救你。它是在 1.0 处的最小显着差异量,即您可以添加到 1.0 以获得不同值的最小值。如果您的结果可能大于或小于 1.0,您将需要不同的 EPSILON。如果您认为您需要 EPSILON 来解决任何问题,那么您很可能会在您的软件中引入一个非常微妙的错误。 您可以考虑使用std::lround,而不是static_cast,它返回最接近的int:int i = std::lround(std::ceil(f))。这将消除对 epsilon 或条件的需要。 【参考方案1】:

人们通常认为浮点运算产生的结果带有小的、不可预测的、准随机错误。此印象不正确。

浮点算术计算尽可能精确18/3 将始终产生正好 61/3 的结果不会正好是三分之一,但它会是最接近三分之一的可表示为浮点数的数字

因此,您展示的示例保证始终有效。至于您建议的“保证地板/天花板”,这不是一个好主意。某些操作序列可以很容易地将错误吹到远远高于1e-10,而某些其他用例将要求1e-10 被正确识别(和上限)为非零。

根据经验,硬编码的 epsilon 值是代码中的错误。

【讨论】:

【参考方案2】:

只要浮点值 x 和 y 准确表示您使用的类型范围内的整数,就没有问题 --x / y 将始终产生一个准确表示的浮点值整数结果。你正在做的转换为 int 总是有效的。

但是,一旦浮点值超出类型 (Representing integers in doubles) 的整数可表示范围,epsilons 就没有帮助。

考虑这个例子。 16777217 是不能精确表示为 32 位 float 的最小整数:

int ix=16777217, iy=97;
printf("%d / %d = %d", ix, iy, ix/iy);
// yields "16777217 / 97 = 172961" which is accurate

float x=ix, y=iy;
printf("%f / %f = %f", x, y, x/y);
// yields "16777216.000000 / 97.000000 = 172960.989691"

在这种情况下,错误为负;在其他情况下(尝试 16777219 / 1549),错误是肯定的。

虽然添加 epsilon 以使 floor 工作很诱人,但它不会大大提高准确性。 当值相差更多数量级时,误差会大于 1,无法保证整数精度。 具体而言,当x/y 超过最大值时。可以表示,误差可以超过1.0,所以epsilon是没有帮助的。

如果这开始发挥作用,您将不得不考虑改变您的数学方法——运算顺序、使用对数等。

【讨论】:

【参考方案3】:

使用双打时可能会出现这样的结果。您可以使用 round 或减去 0.5,然后使用 std::ceil 函数。

【讨论】:

问题是关于使用可表示为整数的操作数和结果的计算。【参考方案4】:

在您列出的具体示例中,我认为这些错误永远不会发生。

std::floor(2000.0 /*Exactly Representable in 32-bit or 64-bit Floating Point Numbers*/ / 1000.0 /*Also exactly representable*/) --> std::floor(2.0 /*Exactly Representable*/) --> 2
std::ceil(18 / 3 /*both treated as ints, might not even compile if ceil isn't properly overloaded....?*/) --> 6
std::ceil(18.0 /*Exactly Representable*/ / 3.0 /*Exactly Representable*/) --> 6

话虽如此,如果您的数学运算依赖于这些函数对于浮点数的行为完全正确,那可能会说明您需要重新考虑/重新检查的设计缺陷。

【讨论】:

以上是关于将 `std::floor()` 和 `std::ceil()` 转换为整数类型是不是总是给出正确的结果?的主要内容,如果未能解决你的问题,请参考以下文章

具有相同值但不同类型的参数的 std::floor 函数的不同值

如何将CString和:: std :: string :: std :: wstring互相转换?

c++ 如何将 std::mutex 和 std::lock_guard 与仿函数一起使用?

C++ 将预先保留的哈希映射(std::unordered_map)与整数键和连续数据数组(std::vector)进行比较

为啥 std::transform 和类似的东西将“for”循环增量转换为(void)?

将数据从 std::vector 传递到 std::valarray 的最有效方法