dcm*_*ody 77 php floating-point precision floating-accuracy
$a = '35';
$b = '-34.99';
echo ($a + $b);
Run Code Online (Sandbox Code Playgroud)
结果在0.009999999999998
怎么了?我想知道为什么我的程序会报告奇怪的结果.
为什么PHP没有返回预期的0.01?
Nul*_*ion 120
因为浮点运算!=实数运算.由于不精确性的差异的图示是,对于一些浮标a和b,(a+b)-b != a.这适用于使用浮点数的任何语言.
由于浮点是具有有限精度的二进制数,因此存在有限数量的可表示数字,这导致精度问题和这样的惊喜.这是另一个有趣的读物:每个计算机科学家应该知道浮点运算.
回到你的问题,基本上没有办法准确地表示二进制34.99或0.01(就像十进制,1/3 = 0.3333 ......),所以使用近似值.要解决此问题,您可以:
使用round($result, 2)结果将其四舍五入到小数点后两位.
使用整数.如果是货币,比如说美元,则将35.00美元存储为3500,将34.99美元存储为3499,然后将结果除以100.
ste*_*esu 49
与所有数字一样,浮点数必须作为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
Run Code Online (Sandbox Code Playgroud)
打破它的组成部分:
Sign Power Mantissa
0 00000100 10110000000000000000000
+ +4 1.1011
+ +4 1 + .5 + .125 + .0625
+ +4 1.6875
Run Code Online (Sandbox Code Playgroud)
运用我们简单的公式:
(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27
Run Code Online (Sandbox Code Playgroud)
换句话说,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
Run Code Online (Sandbox Code Playgroud)
(注意负7是使用2的补码产生的)
应该立即明确,01111100101000111101011100001010看起来不像0.01
但更重要的是,它包含重复小数的截断版本.原始十进制包含重复的"10100011110101110000".我们已将其简化为01000111101011100001010
通过我们的公式将此浮点数转换回十进制,我们得到0.0099999979(请注意,这是针对32位计算机.64位计算机将具有更高的准确性)
如果它有助于更好地理解问题,那么在处理重复小数时,让我们看一下十进制科学记数法.
我们假设我们有10个"盒子"来存储数字.因此,如果我们想要存储像1/16这样的数字,我们会写:
+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)
这显然只是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 |
+---+---+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)
这个新数字0.66667并不完全相同2/3.事实上,它已经过去了0.000003333....如果我们尝试0.66667在3号基础上写作,我们就会得到0.2000000000012...而不是0.2
如果我们采用具有更大重复小数的东西,例如,这个问题可能会变得更加明显1/7.这有6个重复的数字:0.142857142857...
将其存储到我们的十进制计算机中,我们只能显示其中的5个数字:
+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+
Run Code Online (Sandbox Code Playgroud)
这个数字0.14286是关闭的.000002857...
它"接近正确",但它并不完全正确,所以如果我们试图在7号基础上写下这个数字,我们会得到一些可怕的数字而不是0.1.事实上,将其插入Wolfram Alpha我们得到:.10000022320335...
这些小的分数差异应该看起来很熟悉0.0099999979(而不是0.01)
irc*_*ell 15
这里有很多答案可以解释为什么浮点数的运行方式......
但是很少谈论任意精确度(Pickle提到它).如果你想要(或需要)精确的精确度,唯一的方法(至少对于有理数)是使用BC Math扩展(实际上只是一个BigNum,Arbitrary Precision实现...
要添加两个数字:
$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);
Run Code Online (Sandbox Code Playgroud)
会导致12345678901235.1234567890......
这称为任意精度数学.基本上所有数字都是为每个操作解析的字符串,并且逐个数字地执行操作(想想长除法,但是由库完成).所以这意味着它很慢(与常规数学结构相比).但它非常强大.您可以对具有精确字符串表示的任何数字进行乘法,加法,减法,除法,求模和取幂.
所以你不能1/3100%准确,因为它有一个重复的小数(因此是不合理的).
但是,如果你想知道1500.0015平方是什么:
使用32位浮点数(双精度)给出估计结果:
2250004.5000023
Run Code Online (Sandbox Code Playgroud)
但是bcmath给出了确切的答案:
2250004.50000225
Run Code Online (Sandbox Code Playgroud)
这一切都取决于您需要的精度.
另外,还有其他需要注意的地方.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 ...
bcad()在这里可能有用。
<?PHP
$a = '35';
$b = '-34.99';
echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);
?>
Run Code Online (Sandbox Code Playgroud)
(为了清晰起见,输出效率低下)
第一行给了我 0.009999999999998。第二个给我 0.01
| 归档时间: |
|
| 查看次数: |
37395 次 |
| 最近记录: |