了解从整数到浮点数的强制转换

时间:2018-05-13 18:48:54

标签: c floating-point int precision floating-point-conversion

有人可以在32位机器上解释这种奇怪的输出吗?

where g1.gen_id < g2.gen_id

输出

#include <stdio.h>

int main() {
  printf("16777217 as float is %.1f\n",(float)16777217);
  printf("16777219 as float is %.1f\n",(float)16777219);

  return 0;
}

奇怪的是,16777217投射到较低值,而16777219投射到较高值...

4 个答案:

答案 0 :(得分:14)

在IEEE-754基本32位二进制浮点格式中,-16,777,216到+16,777,216的所有整数都是可表示的。从16,777,216到33,554,432,只有偶数可以表示。然后,从33,554,432到67,108,864,只有四的倍数是可表示的。 (由于这个问题不需要讨论哪些数字可以表示,我将省略解释,并将此视为理所当然。)

最常见的默认舍入模式是将精确的数学结果舍入到最接近的可表示值,如果是平局,则舍入到可表示的值,该值在其有效位的低位中为零。

16,777,217在两个可表示的值16,777,216和16,777,218之间是等距的。这些值表示为100000000000000000000000 2 •2 1 和100000000000000000000001 2 •2 1 。前者在其重要位置的低位有0,因此选择它作为结果。

16,777,219在两个可表示的值16,777,218和16,777,220之间是等距的。这些值表示为100000000000000000000001 2 •2 1 和100000000000000000000010 2 •2 1 。后者在其重要位置的低位有0,因此选择它作为结果。

答案 1 :(得分:3)

你可能听说过&#34; precision&#34;的概念,如&#34;这个小数表示有3位精度&#34;。

这在定点表示中非常容易思考。如果我有三位精度超过小数,那么我可以精确地代表1/2 = 0.5,我可以精确地代表1/4 = 0.25,我可以完全代表1/8 = 0.125,但如果我试着代表1/16,我可以得到0.0625;我要么必须满足0.062或0.063。

但那是定点。您正在使用的计算机使用浮点,这很像科学记数法。您将获得一定数量的有效数字总计,而不仅仅是小数点右侧的数字。例如,如果浮点格式的精度为3位十进制数,则可以表示0.123而不是0.1234,可以表示0.0123和0.00123,但不能表示0.01234或0.001234。如果小数点左边有数字,那么这些数字会从小数点右边的数字中移除。您可以使用1.23而不是1.234和12.3但不能使用12.34和123.0但不能使用123.4或123.anythingelse。

并且 - 你现在可以看到这个模式 - 如果你使用只有三位有效数字的浮点格式,你就不能完全准确地表示所有大于999的数字即使他们没有分数部分。您可以代表1230但不代表1234和12300但不代表12340。

这样的十进制浮点格式。另一方面,您的计算机使用二进制浮点格式,最终需要考虑一些棘手的问题。我们没有确切的小数位数&#39;精确度很高,而且无法准确表示的数字即使是10或100的倍数也不会很好。

特别是,大多数机器上的float类型都有24个二进制位,精度达到6-7个十进制数字&#39;值得精确。对于像16777217这样的数字来说,这显然是不够的。

那么数字16777216和16777220来自哪里?正如Eric Postpischil已经解释的那样,它最终是因为它们是2的倍数。如果我们看一下附近数字的二进制表示,那么模式就会变得清晰:

16777208     111111111111111111111000
16777209     111111111111111111111001
16777210     111111111111111111111010
16777211     111111111111111111111011
16777212     111111111111111111111100
16777213     111111111111111111111101
16777214     111111111111111111111110
16777215     111111111111111111111111
16777216    1000000000000000000000000
16777218    1000000000000000000000010
16777220    1000000000000000000000100

16777215是可以用24位精确表示的最大数字。之后,您只能表示偶数,因为低位是第25位,基本上必须为0.

答案 2 :(得分:1)

类型float不能保持那么重要。有效数字只能容纳24位。其中23个是存储的,第24个是1并且没有存储,因为有效数据是标准化的。

read this&#34; [ - 16777216,16777216]中的整数可以准确表示&#34; ,但是你的超出了该范围。

答案 3 :(得分:1)

浮动表示遵循类似于我们在日常生活中使用的方法,我们称之为指数表示。这是一个使用多个数字的数字,我们认为这些数字足以真实地表示该值,我们将其称为尾数或重要数字,我们将乘以基数或基数值,将其提升为我们称之为指数的幂。用简单的话说:

num*base^exp

我们通常使用10作为基础,因为我们手中有10个手指,因此我们习惯使用1e2这样的数字,即100=1*10^2

当然我们很遗憾地使用指数表示这么小的数字,但我们更倾向于在处理非常大的数字时使用它,或者更好的是,当我们的数字具有我们认为足以表示的数字位数时我们正在评估的实体

正确的位数可以是我们可以处理的数量,或工程应用程序所需的数量。当我们确定需要多少位数时,我们将不再关心如何将真实值与我们将要处理的数值表示相符合。即对于像123456.789e5这样的数字,我们可以理解,加上99单位,我们可以容忍舍入的表示并且无论如何都认为它是可接受的,如果不是,我们应该更改表示并使用具有适当位数的不同表示与12345678900中一样。

在计算机上,当你必须处理非常大的数字时,它不能适合标准整数,或者当你必须表示实数(带小数部分)时,正确的选择是{{1 }或floating浮点表示。它使用我们上面讨论的相同布局,但基数是2而不是10 。这是因为计算机只能有两个手指,即状态double0。我们之前使用的公式代表100,变为:

1

那仍然不是真正的浮点表示,但是给出了这个想法。现在考虑在计算机中浮点格式是标准化的,对于标准浮点数,根据IEE-754,它使用,作为存储器布局(我们将看到为什么假设尾数为1位),23位用于尾数,符号为1位,指数为8位,偏差为-127(这意味着它将在100100*2^0 -126之间,而不需要符号位,值为{{1} }和+127保留用于特殊含义。)

现在考虑使用0作为指数,这意味着值乘以尾数的值0x00给出了23位整数的相同行为。这意味着递增计数如下:

0xff

您将看到打印值线性增加1,直到它使23位饱和并且指数将变为增长。

如果我们的浮点数的基数或基数为10,我们会看到前100个(10 ^ 2)值每10个循环增加,而下一个1000增加100(10) ^ 3)值等。您会看到这对应于我们必须进行的*截断**,因为可用数字的数量有限。

使用二进制基数时会观察到相同的现象,只有在2区间的幂上发生变化。

我们到目前为止讨论的内容称为浮点的非规范化形式,通常使用的是对应的规范化。后者只是意味着存在第24位,而不是存储,总是2^exponent=2^0=1。在平面词中,我们不会使用float f = 0; while(1) { f +=1; printf ("%f\n", f); } 的指数小于1,但我们将其(乘以2)移至0到达第24位,比指数调整到这样一个负值,迫使转换将数字转回原来的值。

还记得我们上面谈过的指数的保留值吗?那么2^24意味着我们有一个非规范化的数字。如果MSbit==1,则exponent==0x00表示exponent==0xff(非数字)或+/-无穷大。

现在应该清楚的是,当我们表达的数字超出重要(尾数)的24位时,我们应该期望实际值的近似取决于我们离nan多远。

现在您使用的号码就在mantissa==0

的边缘
2^24

现在增加1我们有:

2^24=16,277,216

请注意,我们已经触发了+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|1|0|0|1|0|1|1|0|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| = 16,277,215 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ s\______ _______/\_____________________ _______________________/ i v v g exponent mantissa n 第24位,但从现在开始,我们高于24位表示,并且每个可能的进一步表示都是+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0| = 16,277,216 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ s\__ exponent __/\_________________ mantissa __________________/ 的步骤。只需前进2或只能代表偶数(1的倍数)。即设置为1我们的较低有效位:

2^1=2

再次增加:

2^1=2

如您所见,我们无法准确代表16,277,219。在您的代码中:

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1| = 16,277,218
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 s\__ exponent __/\_________________ mantissa __________________/

如上所述,数字格式的选择必须适合用法,浮点只是实数的近似表示,并且我们有责任谨慎使用正确的类型

如果我们需要更高的精度,我们可以使用+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0|1|0|0|1|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0| = 16,277,220 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ s\__ exponent __/\_________________ mantissa __________________/ 或整数// This will print 16777216, because 1 increment isn't enough to // increase the significant that can express only intervals // that are > 2^1 printf("16777217 as float is %.1f\n",(float)16777217); // This will print 16777220, because an increment of 3 on // the base 16777216=2^24 will trigger an exponent increase rounded // to the closer exact representation printf("16777219 as float is %.1f\n",(float)16777219);

为了完整起见,我会在不可约数字的近似表示上添加几个单词。这个数字不能被2的一小部分整除,因此浮点格式的表示将始终不精确,并且需要在转换为十进制表示期间四舍五入到正确的值。

有关详细信息,请参阅:

在线演示小程序: