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

Does casting `std::floor()` and `std::ceil()` to integer type always give the correct result?

我偏执地认为其中一个函数可能会给出这样的错误结果:

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' return 类型。)

人们通常认为浮点运算产生的结果带有小的、不可预测的 quasi-random 错误。这种印象是错误的。

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

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

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

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

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

话虽如此,如果您的数学依赖于这些函数对浮点数的行为完全正确,这可能会说明您需要 reconsider/reexamine.

的设计缺陷

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

只要浮点值 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 没有帮助。

如果出现这种情况,您将不得不考虑改变您的数学方法——运算顺序、使用对数等。