什么时候静态播放ceil的结果会影响结果?

时间:2018-01-05 16:04:03

标签: c++ floating-point integer static-cast ceiling

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

2 个答案:

答案 0 :(得分:1)

  

例如,在内部,最接近13,000,000的浮点数可能是:12999999.999999。

在任何正常的浮点格式中都不可能。数字的浮点表示等同于 M b e ,其中 b 是一个固定的基数(例如,2表示二进制浮点数), M e 是整数,对它们的值有一些限制。为了表示像13,000,000- x 这样的值,其中 x 是一个小于1的正值, e 必须是负数(因为 M b e 对于非负 e 是整数)。如果是,那么 M •b 0 是一个大于 M 的整数• b e ,因此大于13,000,000,因此13,000,000可以表示为 M&#39; b 0 ,其中 M&#39; 是一个小于 M 的正整数,因此符合 M 的允许值范围(在任何正常浮点格式)。 (也许一些奇怪的浮点格式可能会在 M e 上施加一个奇怪的范围来阻止这种情况,但是没有正常的格式。)

关于您的代码:

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不是。

  

如果我这样做,行为似乎会恢复正常:ceil(8&#39; 388&#39; 609.5F)将正确返回8,388,610。

8,388,609.5在您的系统上以float格式无法表示,因此它被“舍入为最接近,与均匀关系”的规则四舍五入。两个最接近的可表示值为8,388,609和8,388,610。由于它们相距很远,结果是8,388,610。该值传递给ceil,当然返回8,388,610。

  

在Visual Studio 2015上,我得到了8,388,609,这是一个可怕的小安全范围。

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

答案 1 :(得分:0)

Floating point numbers由3个整数 cb q 表示:

  • c 是尾数(因此对于数字: 12,999,999.999999 c 12,999,999,999,999
  • q 是指数(因此对于数字: 12,999,999.999999 q -6
  • b 是基础(IEEE-754要求 b 10 2 ;在 b 上方的表示是 10

由此可以很容易地看出,具有表示 12,999,999.999999 的浮点数也能够使用 c 表示 13,000,000.000000 1,300,000,000,000 q -5

这个例子有点人为,因为所选择的 b 10 ,其中几乎所有实现中所选择的基数都是 2 。但值得指出的是,即使 b 2 q 也可以作为尾数的左移或右移。

接下来让我们来谈谈一个范围。显然,32位浮点不能表示由32位整数表示的所有整数,因为浮点数也必须代表更多或更多的数字。由于指数只是简单地移动尾数,浮点数总是完全表示可以用它的尾数表示的每个整数。鉴于传统的IEEE-754二进制基础浮点数:

  • 32位(float)有一个24位尾数,因此它可以表示[ -16,777,215 16,777,215 ] <范围内的所有整数/ LI>
  • 64位(double)有一个53位的尾数,因此它可以表示[ -9,007,199,254,740,991 9,007,199,254,740,991 ] <范围内的所有整数/ LI>
  • 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, 40191

[source]

提供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要求:

  

通过选择保留所选字大小和格式中最高有效位(MSB)的最小可表示指数,使表示唯一。此外,指数不是直接表示,而是添加偏差,使得最小的可表示指数表示为1,其中0表示次正规数

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

  • c = 13,000,000,000,000 所以MSB将用于代表的数字
  • q = 6 这有点令人困惑,这是引入偏见的原因;逻辑 q = -6 但偏差设置为当 q = 0 时,只有 c 的MSB为紧靠小数点左侧,意味着 c = 13,000,000,000,000 q = 0 b = 10 将代表 1.3 < / em>的
  • b = 10 上述规则实际上只需要base-2但我已经展示了它们,因为它们适用于base-10以便解释

转换回base-2这意味着numeric_limits<T>::digits - 1 q 在小数位后只有零。 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