类型转换与算术运算

3

今天我在看一本参考书时遇到了一个例子,它说:

echo (int) ((0.1 + 0.7) * 10); // output 7

因为((0.1 + 0.7) * 10)在内部被计算为7.999999,并在转换为int时结果为7
我也发现了这一点。请参见Codepad
但是当我尝试更多的例子时,我发现了一些奇怪的事情,例如- echo (int)((0.2 + 0.7) * 10); // 输出9 (Codepad) echo (int)((0.7 + 0.7) * 10); // 输出14 (Codepad)
还有很多其他的例子。每次我改变值时,它都会给出正确的答案。
我想知道为什么只有((0.1 + 07) * 10)的结果与其他结果不同。
这真的很奇怪吗?还是我漏掉了什么?

4
这只发生在“0.1 + 0.7”的情况下。即使我们改变顺序,对于其他数字它们会正确计算。不知道这里发生了什么,但这个问题是我最喜欢的问题..... :) - Yogesh Suthar
1
让我们看看是否有 PHP 领域的 Jon Skeet 能够回答这个问题。 - Rikesh
2
你似乎没有尝试过 (0.6 + 0.7) * 10 - Daniel Fischer
2
提示:在二进制中,0.1有无限多的数字。 - Álvaro González
2
@Coderanonymous:你提供的链接中没有一个明确说明为什么 .1+.7 小于 8 而 .2+.7 不小于 9。也就是说,虽然它们可能提供了最终回答问题的信息,但它们实际上没有回答这个问题。 - Eric Postpischil
显示剩余7条评论
5个回答

3
在通用的双精度格式中,数字由一个符号位、一个11位指数和一个53位分数部分组成,称为尾数。尾数总是一个53位的非负整数除以252(也可以写成一个二进制数字、一个基数点和52个二进制数字)。
.1无法被准确表示。它的指数为-4,尾数为7205759403792794/252。也就是说,最接近.1的double是7205759403792794•2-52•2-4=0.1000000000000000055511151231257827021181583404541015625。
最接近.7的double具有6305039478318694/252的尾数和-1的指数;它是6305039478318694•2-52•2-1=0.6999999999999999555910790149937383830547332763671875。
当你把这两个数字相加时,结果是0.7999999999999999611421941381195210851728916168212890625。这在double中也不能准确表示;它必须四舍五入到最近可表示的值,当你乘以10时,它还必须再次四舍五入。然而,你可以看到总和小于.8。最终结果小于8,所以转换为整数会将其截断为7。 double最接近.8是0.8000000000000000444089209850062616169452667236328125。当你将它加到0.1000000000000000055511151231257827021181583404541015625时,总和是0.9000000000000000499600361081320443190634250640869140625。正如你所看到的,它大于.9。四舍五入并乘以10的最终结果将是9或更大,因此将其转换为整数将产生9。
其他一些你尝试过的值没有向下舍入只是偶然。每个不准确可表示的值都落在两个可表示值之间,一个更高一个更低。有些更接近较高的值,有些更接近较低的值,你只是碰巧选择了更接近较高可表示值并被向上舍入的值。

1

PHP文档中看到:

永远不要将未知的分数转换为整数,因为这可能会导致意外的结果

<?php
    echo (int) ( (0.1+0.7) * 10 ); // echoes 7!
?>

所以,结果很奇怪,因为意外的((0.1+0.7)*10的内部表示就像是7.99999...)

有关浮点精度的更多信息,请参阅PHP文档。


0

这是由于使用浮点数进行计算时的舍入误差导致的。 例如:

0.1 + 0.7 = 0.7999999999
0.7999999999 * 10 = 7.999999999
floor(7.999999999) = 7

解决这个问题的方法是在进行类型转换之前先四舍五入。

0

计算机处理的是绝对值;它们不能在位级别上明确表示分数。这可以通过使用浮点表示来解决,这是一种表示实数近似值的方法。

不幸的是,有些数字只是无法100%准确地表示,这导致在处理浮点运算时出现不精确。

要了解更多关于为什么会出现这种情况的信息,请进行一些关于浮点表示的研究。但这是一个相当数学技术性的主题。

编辑

让我澄清一下。我们都知道计算机处理二进制。它们读取、写入和处理的位只能是1或0。

32位处理器通常将内存分成4字节块。因此,int和float的默认大小为4字节或32位。在二进制中表示整数很容易。数字8是:00000000 00000000 00000000 00001000。但计算机如何表示十进制数呢?请记住,它只能看到1和0;它不能在它们中间放置“.”!

定点表示法(例如,将前16位表示为整数值(小数点前的部分),后16位表示为小数值)显着限制了可以表示的数字范围,因为它将最大数字减少到16位int,并且可能浪费小数点后面的所有位,这些位可能不需要。

因此,计算机使用一种称为浮点表示法的技术,其中使用编码指数对数字进行缩放。因此,数字的一部分是指数,另一部分是分数。与定点表示法相比,这极大地增加了可能的数字范围。但是,有些数字无法完全精确地表示。

这就是为什么任何处理货币的计算机系统都不会将值存储为浮点数(例如,£1.10将始终存储为110p)。任何需要精度的系统都应该尽可能多地执行整数运算,并将任何除法转换为浮点数作为最后一步。

请注意,这不仅是PHP问题,它存在于所有语言中。例如,JavaScript:

alert((0.1+0.7)*10); // alerts 7.9999999999

-1

使用 intval 代替,它更加可靠。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接