static_casting ceil 的结果什么时候会妥协?

When Will static_casting the Result of ceil Compromise the Result?

static_cast 从浮点数到整数只是去掉了数字的小数点。例如 static_cast<int>(13.9999999) 产生 13.

并非所有整数都可以表示为浮点数。例如,在内部最接近 float 到 13,000,000 的可能是:12999999.999999.

在这个假设的案例中,我希望从以下方面得到意想不到的结果:

const auto foo = 12'999'999.5F;
const auto bar = static_cast<long long>(ceil(foo));

我的假设是,这种崩溃确实会在某个时候发生,即使不一定是在 13,000,000 时。我只想知道我可以信任的范围 static_cast<long long>(ceif(foo))?

For example internally the closest float to 13,000,000 may be: 12999999.999999.

这在任何普通的浮点格式中都是不可能的。数字的浮点表示等价于Mbe,其中 b 是固定基数(例如 ,2 表示二进制浮点数)和 Me 是整数,它们的值有一些限制。为了表示像 13,000,000-x 这样的值,其中 x 是小于 1 的正值,e 必须为负数(因为 Mbe 对于非负 e 是一个整数)。如果是,那么 M•b0 是一个大于 M 的整数be,所以大于13,000,000,所以13,000,000可以表示为M'b0,其中M'为小于[=30的正整数=]M 因此适合 M 的允许值范围(在任何正常的浮点格式中)。 (也许一些奇怪的浮点格式可能会在 Me 上强加一个奇怪的范围来阻止这种情况,但没有正常的格式。)

关于您的代码:

auto test = 0LL;
const auto floater = 0.5F;

for(auto i = 0LL; i == test; i = std::ceil(i + floater)) ++test;

cout << test << endl;

i为8,388,608时,8,388,608 + .5的数学结果为8,388,608.5。这在您的系统上无法以 float 格式表示,因此四舍五入为 8,388,608。这个的ceil是8,388,608。此时test为8,388,609,循环停止。所以这段代码没有证明 8,388,608.5 是可表示的,而 8,388,609 不是。

Behavior seems to return to normal if I do: ceil(8'388'609.5F) which will correctly return 8,388,610.

8,388,609.5 在您的系统上无法以 float 格式表示,因此它按照“四舍五入到最接近,并列到偶数”的规则四舍五入。两个最接近的可表示值是 8,388,609 和 8,388,610。由于它们相距相等,因此结果为 8,388,610。该值被传递给 ceil,它当然返回了 8,388,610。

On Visual Studio 2015 I got 8,388,609 which is a horrifying small safe range.

在 IEEE-754 基本 32 位二进制格式中,从 -16,777,216 到 +16,777,216 的所有整数都是可表示的,因为该格式具有 24 位有效数字。

Floating point numbers 由 3 个 整数 cbq 表示,其中:

  • c 是尾数(因此对于数字:12,999,999.999999 c 将是 12,999,999,999,999)
  • q 是指数(因此对于数字:12,999,999.999999 q 将是 -6)
  • b 是基础(IEEE-754 要求 b102;在上面的表示中 b10)

由此不难看出,可以表示12,999,999.999999的浮点数也可以表示13,000,000.000000 c1,300,000,000,000q-5

这个例子有点做作,因为选择的 b10,而在几乎所有的实现中,选择的基础是 2。但值得指出的是,即使 b2q 也起到左移或左移的作用尾数右边。


接下来我们在这里说一个范围。显然,一个 32 位浮点数不能表示一个 32 位整数表示的所有整数,因为浮点数还必须表示那么多或大或小的数。由于指数只是简单地移动尾数,浮点数总是可以 精确地 表示它的尾数可以表示的每个整数。给定传统的 IEEE-754 二进制基浮点数:

  • 一个 32 位 (float) 有一个 24 位尾数,因此它可以表示 [-16,777,215, 范围内的所有整数16,777,215]
  • 64 位 (double) 有一个 53 位尾数,因此它可以表示 [-9,007,199,254,740,991 范围内的所有整数9,007,199,254,740,991]
  • 128 位(long double 取决于实现)有一个 113 位尾数,因此它可以表示 [-103,845,937,170,696,552,570,609,926,584,40,191 范围内的所有整数, 103,845,937,170,696,552,570,609,926,584,40,191]

[source]

provides digits 作为为给定浮点类型查找此数字的方法。 (尽管不可否认,即使 long long 也太小而无法表示 113 位尾数。)例如,float 的最大尾数可以通过以下方式找到:

(1LL << numeric_limits<float>::digits) - 1LL

彻底解释了尾数之后,让我们重新访问指数部分来讨论如何 floating point is actually stored。取 13,000,000.0 可以表示为:

  • c = 13, q = 6, b = 10
  • c = 130, q = 5, b = 10
  • c = 1,300, q = 4, b = 10

等等。对于传统的二进制格式 IEEE-754 要求:

The representation is made unique by choosing the smallest representable exponent that retains the most significant bit (MSB) within the selected word size and format. Further, the exponent is not represented directly, but a bias is added so that the smallest representable exponent is represented as 1, with 0 used for subnormal numbers

如果我们的尾数有 14 个小数位,以更熟悉的 base-10 来解释这一点,实现将如下所示:

  • c = 13,000,000,000,000 因此 MSB 将用于表示的数字
  • q = 6 这有点令人困惑,这是这里引入偏差的原因;逻辑上 q = -6 但偏差设置为当 q = 0 只有 MSB c紧挨着小数点左边,意思是c = 13,000,000,000,000, q = 0 , b = 10 将代表 1.3
  • b = 10 同样,上述规则实际上只适用于 base-2,但我已经展示了它们,因为它们将适用于 base-10 以进行解释

转换回 base-2 这意味着 numeric_limits<T>::digits - 1q 在小数点后只有零。 ceil 仅在数字的小数部分有效。

这里要解释的最后一点是 ceil 会产生影响的范围。在浮点数的指数大于 numeric_limits<T>::digits 继续增加后,它只会在结果数中引入尾随零,因此当 q 大于时调用 ceil或等于 numeric_limits<T>::digits - 2LL。并且由于我们知道 c 的 MSB 将在数字中使用,这意味着 c 必须小于 (1LL << numeric_limits<T>::digits - 1LL) - 1LL 因此对于 ceil 对传统二进制 IEEE-754 浮点数有影响:

  • 32 位 (float) 必须小于 8,388,607
  • 64 位 (double) 必须小于 4,503,599,627,370,495
  • 128 位(long double 取决于实现)必须小于 5,192,296,858,534,827,628,530,496,329,220,095