$a = '35';
$b = '-34.99';
echo ($a + $b);
结果为0.009999999999998
这是怎么回事?我想知道为什么我的程序会报告奇怪的结果。
为什么PHP不返回预期的0.01?
答案 0 :(得分:117)
因为浮点运算!=实数运算。对于某些浮点a
和b
,(a+b)-b != a
,由于不精确导致的差异的说明是。这适用于使用浮点数的任何语言。
由于floating point是具有有限精度的二进制数,因此representable numbers的数量有限,导致accuracy problems和这样的惊喜。这是另一个有趣的读物:What Every Computer Scientist Should Know About Floating-Point Arithmetic。
回到你的问题,基本上没有办法准确地表示二进制34.99或0.01(就像十进制,1/3 = 0.3333 ......),所以使用近似值。要解决这个问题,您可以:
在结果上使用round($result, 2)
将其四舍五入到小数点后两位。
使用整数。如果这是货币,比如说美元,则将35.00美元存储为3500,将34.99美元存储为3499,然后将结果除以100。
答案 1 :(得分:47)
浮点数与所有数字一样,必须以0和1的字符串形式存储在内存中。它是计算机的所有部分。浮点与整数的不同之处在于我们在想要查看它们时如何解释0和1。
一位是“符号”(0 =正,1 =负),8位是指数(范围从-128到+127),23位是称为“尾数”(分数)的数字。所以(S1)(P8)(M23)的二进制表示具有值(-1 ^ S)M * 2 ^ P
“尾数”采用特殊形式。在正常的科学记数法中,我们显示“一个地方”以及分数。例如:
4.39 x 10 ^ 2 = 439
在二进制中,“一个人的位置”是一个位。由于我们忽略科学记数法中所有最左边的0(我们忽略任何无关紧要的数字),所以第一位保证为1
1.101 x 2 ^ 3 = 1101 = 13
由于我们保证第一位将为1,因此在存储数字时我们会删除此位以节省空间。所以上面的数字只存储为101(尾数)。假设前导1
举个例子,我们来看二进制字符串
00000010010110000000000000000000
将其分解为组件:
Sign Power Mantissa
0 00000100 10110000000000000000000
+ +4 1.1011
+ +4 1 + .5 + .125 + .0625
+ +4 1.6875
应用我们的简单公式:
(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27
换句话说,00000010010110000000000000000000浮点数为27(根据IEEE-754标准)。
然而,对于许多数字,没有确切的二进制表示。很像1/3 = 0.333 ....永远重复,1/100是0.00000010100011110101110000 .....重复“10100011110101110000”。但是,32位计算机无法以浮点存储整个数字。所以它是最好的猜测。
0.0000001010001111010111000010100011110101110000
Sign Power Mantissa
+ -7 1.01000111101011100001010
0 -00000111 01000111101011100001010
0 11111001 01000111101011100001010
01111100101000111101011100001010
(注意负7是使用2的补码产生的)
应该立即明白,01111100101000111101011100001010看起来不像0.01
然而,更重要的是,它包含重复小数的截断版本。原始十进制包含重复的“10100011110101110000”。我们已将其简化为01000111101011100001010
通过我们的公式将此浮点数转换回十进制,我们得到0.0099999979(请注意,这是针对32位计算机.64位计算机将具有更高的准确性)
如果有助于更好地理解问题,那么在处理重复小数时,让我们看一下十进制科学记数法。
我们假设我们有10个“盒子”来存储数字。因此,如果我们想存储一个像1/16的数字,我们会写:
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+
这显然只是6.25 e -2
,其中e
是*10^(
的简写。我们为小数分配了4个方框,即使我们只需要2个(用零填充),我们已经为符号分配了2个方框(一个用于数字的符号,一个是指数的符号)
使用10个这样的框,我们可以显示从-9.9999 e -9
到+9.9999 e +9
这适用于4位或更少小数位的任何内容,但是当我们尝试存储2/3
这样的数字时会发生什么?
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
这个新号码0.66667
并不完全等于2/3
。事实上,它是0.000003333...
的关闭。如果我们尝试在基数3中写0.66667
,我们会得到0.2000000000012...
而不是0.2
如果我们采用具有较大重复小数的内容(例如1/7
),则此问题可能会更加明显。这有6个重复的数字:0.142857142857...
将此存储到我们的十进制计算机中,我们只能显示其中的5位数字:
+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
此号码0.14286
已被.000002857...
它“接近正确”,但它不是 完全正确的 ,所以如果我们试图在基数7中写这个数字,我们会得到一些可怕的数字,而不是0.1
。事实上,将其插入Wolfram Alpha我们得到:.10000022320335...
0.0099999979
(与0.01
相对)
答案 2 :(得分:14)
这里有很多答案可以解释为什么浮点数的运行方式......
但是很少谈论任意精确度(Pickle提到它)。如果你想要(或需要)精确的精确度,唯一的方法(至少对于有理数)是使用BC Math扩展(实际上只是BigNum, Arbitrary Precision实现......
添加两个数字:
$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);
将导致12345678901235.1234567890
...
这称为任意精度数学。基本上所有数字都是为每个操作解析的字符串,并且逐个数字地执行操作(想想长除法,但是由库完成)。所以这意味着它很慢(与常规数学结构相比)。但它非常强大。您可以对任何具有精确字符串表示的数字进行乘法,加法,减法,除法,求模和取幂。
因此,您不能以100%的准确度执行1/3
,因为它具有重复的小数(因此不合理)。
但是,如果你想知道1500.0015
平方是什么:
使用32位浮点数(双精度)给出估计结果:
2250004.5000023
但是bcmath给出了确切答案:
2250004.50000225
这一切都取决于你需要的精确度。
此外,还有其他需要注意的地方。 PHP只能表示32位或64位整数(取决于您的安装)。因此,如果一个整数超过native int类型的大小(对于32位为21亿,对于有符号的int为9.2 x10 ^ 18或92亿十亿),PHP会将int转换为float。虽然这不是一个问题(因为所有小于系统浮点精度的int都可以直接表示为浮点数),如果你尝试将两者相乘,它将失去很大的精度。
例如,给定$n = '40000000002'
:
作为一个数字,$n
将是float(40000000002)
,这很好,因为它是完全代表的。但如果我们对它进行调整,我们得到:float(1.60000000016E+21)
作为一个字符串(使用BC数学),$n
将完全是'40000000002'
。如果我们对它进行调整,我们得到:string(22) "1600000000160000000004"
...
因此,如果您需要具有大数字或有理小数点的精度,您可能需要查看bcmath ......
答案 3 :(得分:2)
使用PHP的round()
函数:http://php.net/manual/en/function.round.php
这个答案解决了问题,但没有解释原因。我认为很明显[我也是用C ++编程,所以对我来说很明显;]],但如果没有,让我们说PHP有它自己的计算精度,并且在特定情况下它返回了关于该计算的大部分符合信息
答案 4 :(得分:2)
bcadd()在这里可能很有用。
<?PHP
$a = '35';
$b = '-34.99';
echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);
?>
(为清晰起见,输出效率低)
第一行给我0.009999999999998。 第二个给我0.01
答案 5 :(得分:1)
因为0.01不能精确地表示为二元分数序列的总和。这就是浮点数存储在内存中的方式。
我想这不是你想听到的,但它是问题的答案。如何修复看其他答案。
答案 6 :(得分:1)
我的php返回0.01 ...
也许它有php版本的todo,(我使用5.2)答案 7 :(得分:1)
<强> [解决] 强>
每个数字都将通过二进制值(例如0,1)保存在计算机中。在单精度数中占用32位。
浮点数可以表示为:1位表示符号,8位表示指数,23位称为尾数(分数)。
看下面的例子:
0.15625 = 0.00101 = 1.01 * 2 ^( - 3)
符号:0表示正数,1表示负数,在这种情况下为0。
指数:01111100 = 127 - 3 = 124。
注意:偏差= 127因此偏差指数= -3 +“偏差”。在单精度中,偏差为127,因此在此示例中,偏差指数为124;
在小数部分,我们有:1.01平均值:0 * 2 ^ -1 + 1 * 2 ^ -2
数字1(1.01的第一个位置)不需要保存,因为当以这种方式出现浮动数时,第一个数字总是1。 例如转换:0.11 =&gt; 1.1 * 2 ^( - 1),0.01 =&gt; 1 * 2 ^( - 2)
另一个示例显示总是删除第一个零:0.1将呈现1 * 2 ^( - 1)。所以第一个alwasy是1。 目前的1 * 2 ^( - 1)数将是:
最后:原始二进制文件是: 0 01111110 00000000000000000000000
在此处查看:http://www.binaryconvert.com/result_float.html?decimal=048046053
现在,如果您已经了解了如何保存浮点数。如果数字不能以32位(简单精度)保存,会发生什么。
例如:十进制。 1/3 = 0.3333333333333333333333因为它是无限的我想我们有5位来保存数据。再说一遍这不是真的。只是假设。因此保存在计算机中的数据将是:
0.33333.
现在当加载计算机的数字再次计算时:
0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 + 3*10^-5.
关于这个:
$a = '35';
$b = '-34.99';
echo ($a + $b);
结果为0.01(十进制)。现在让我们用二进制显示这个数字。
0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)
点击此处:http://www.binaryconvert.com/result_double.html?decimal=048046048049
因为(01011100001010001111)重复就像1/3一样。因此,计算机无法将此数字保存在内存中。它必须牺牲。这导致计算机不准确。
高级强> (你必须掌握数学知识) 那么为什么我们可以轻松地以十进制显示0.01而不是二进制。
假设二进制的分数为0.01(十进制)是有限的。
So 0.01 = 2^x + 2^y... 2^-z
0.01 * (2^(x+y+...z)) = (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists.
=> So 0.01 (decimal) must be infine in binary.
答案 8 :(得分:0)
使用number_format(0.009999999999998, 2)
或$res = $a+$b; -> number_format($res, 2);
会不会更容易?