将`std :: floor()`和`std :: ceil()`转换为整数类型总能得到正确的结果吗?

时间:2016-01-25 16:04:38

标签: c++ math floating-accuracy arithmetic-expressions

我很偏执,其中一个函数可能会给出不正确的结果:

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);
    }
}

(注意:我假设给定的&#39; long double&#39;参数适合&#39; intmax_t&#39;返回类型。)

4 个答案:

答案 0 :(得分:20)

人们常常会觉得浮点运算会产生小的,不可预测的,准随机错误的结果。这种印象不正确。

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

因此,您展示的示例始终有效。至于你建议的“保证楼/天花板”,这不是一个好主意。某些操作序列很容易将错误推到1e-10以上,而某些其他用例需要1e-10才能正确识别(和ceil'ed)为非零。

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

答案 1 :(得分: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

话虽如此,如果你的数学运算依赖于这些函数对浮点数的表现完全正确,这可能会说明你需要重新考虑/重新审视的设计缺陷。

答案 2 :(得分:1)

只要浮点值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 :(得分:-6)

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