PHP - Floating Number Precision
1 2 3 | $a = '35'; $b = '-34.99'; echo ($a + $b); |
结果为0.0099999999998
这是怎么回事?我想知道为什么我的程序总是报告奇怪的结果。
为什么php不返回预期的0.01?
因为浮点运算!=实数算术。由于不精确性而产生的差异的一个例子是,对于一些浮动的
因为浮点数是精度有限的二进制数,所以有有限数量的可表示数,这会导致精度问题和这样的意外。这是另一本有趣的书:每一个计算机科学家都应该知道什么是浮点运算。
回到你的问题上,基本上没有办法精确地用二进制表示34.99或0.01(就像十进制一样,1/3=0.3333…),所以使用近似值代替。要解决此问题,您可以:
对结果使用
使用整数。如果这是货币,比如美元,那么存储35.00美元为3500美元,34.99美元为3499美元,然后将结果除以100。
很遗憾,PHP没有其他语言那样的十进制数据类型。
浮点数和所有数字一样,必须以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
例如,让我们以二进制字符串为例
1 | 00000010010110000000000000000000 |
将其分解为组件:
1 2 3 4 5 | Sign Power Mantissa 0 00000100 10110000000000000000000 + +4 1.1011 + +4 1 + .5 + .125 + .0625 + +4 1.6875 |
应用我们的简单公式:
1 2 3 4 | (-1^S)M*2^P (-1^0)(1.6875)*2^(+4) (1)(1.6875)*(16) 27 |
换句话说,00000001011000000000000000000000是27个浮点(根据IEEE-754标准)。
然而,对于许多数字,没有精确的二进制表示。就像1/3=0.333……永远重复,1/100是0.000001010001111010110000…..重复"1010011111010111000"。然而,32位计算机不能将整个数字存储在浮点中。所以这是最好的猜测。
1 2 3 4 5 6 7 | 0.0000001010001111010111000010100011110101110000 Sign Power Mantissa + -7 1.01000111101011100001010 0 -00000111 01000111101011100001010 0 11111001 01000111101011100001010 01111100101000111101011100001010 |
(注意负7是用2的补码产生的)
应该立即清楚的是,01111001010001111010111000001010与0.01完全不同。
然而,更重要的是,它包含了重复小数的截断版本。原始的十进制包含一个重复的"10100011111010111000"。我们把它简化为010001111010111000001010。
通过我们的公式将这个浮点数转换回十进制,我们得到0.0099999979(注意这是针对32位计算机的)。一台64位的计算机会有更高的精度)
十进制等价物如果它有助于更好地理解这个问题,那么让我们看看十进制科学记数法在处理重复的小数时。
假设我们有10个"盒子"来存储数字。因此,如果我们想存储1/16这样的数字,我们会写:
1 2 3 | +---+---+---+---+---+---+---+---+---+---+ | + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 | +---+---+---+---+---+---+---+---+---+---+ |
这显然只是
使用这样的10个框,我们可以显示从
这对于小数点后4位或更少的数字都可以,但是当我们试图存储一个像
1 2 3 | +---+---+---+---+---+---+---+---+---+---+ | + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 | +---+---+---+---+---+---+---+---+---+---+ |
这个新数字
如果我们使用更大的重复小数,比如
将其存储到我们的十进制计算机中,我们只能显示其中5个数字:
1 2 3 | +---+---+---+---+---+---+---+---+---+---+ | + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 | +---+---+---+---+---+---+---+---+---+---+ |
这个数字,
它是"接近正确的",但它并不完全正确,因此如果我们试图以7为基数写这个数字,我们会得到一个可怕的数字而不是
这些细微的分数差异对您的
这里有很多关于浮点数为什么按它们的方式工作的答案…
但很少有关于任意精确性的讨论(皮克提到过)。如果您想要(或需要)精确的精度,唯一的方法是使用BC数学扩展(它实际上只是一个bignum,任意精度的实现)。
要添加两个数字:
1 2 3 |
会导致
这就是所谓的任意精度数学。基本上,所有的数字都是字符串,对每个操作进行解析,并以数字为基础执行操作(考虑长除法,但由库完成)。这意味着它相当慢(与常规的数学构造相比)。但它非常强大。您可以乘、加、减、除、求模和指数化任何具有精确字符串表示形式的数字。
所以你不能100%准确地进行
但是,如果你想知道
使用32位浮点(双精度)得出以下估计结果:
1 | 2250004.5000023 |
但BCMath给出了以下确切答案:
1 | 2250004.50000225 |
这完全取决于你需要的精度。
另外,这里还有一些需要注意的地方。PHP只能表示32位或64位整数(取决于您的安装)。因此,如果一个整数超过了本机int类型的大小(32位为21亿,有符号int为9.2 x10^18,或92亿),php将把int转换成一个浮点。虽然这并不是一个立即出现的问题(因为所有小于系统浮点精度的整数在定义上都直接表示为浮点数),但如果尝试将两个整数相乘,它将失去显著的精度。
例如,给定
作为一个数字,
作为一个字符串(使用bc-math),
因此,如果您需要大数字或有理小数点的精度,您可能需要研究BCMath…
bcadd()在这里可能很有用。
1 2 3 4 5 6 7 8 9 10 |
(为了清晰起见,输出效率低下)
第一行给出0.0099999999998。第二个给我0.01
使用php的
这个答案解决了问题,但不能解释为什么。我认为这是显而易见的[我也在C++编程,所以对我来说是显而易见的],但是如果不是,假设PHP有它自己的计算精度,在那个特定的情况下它返回了关于该计算的最符合的信息。
[解决]
每个数字都将以二进制值(如0、1)保存在计算机中。在单精度数字中占32位。
浮点数可以表示为:1位表示符号,8位表示指数,23位表示尾数(分数)。
请看下面的示例:
0.15625=0.00101=1.01*2^(-3)
符号:0表示正数,1表示负数,本例为0。
指数:0111100=127-3=124。
注:偏差=127,所以偏差指数=?3+偏差。在单精度中,偏差为127,因此在本例中,偏差指数为124;
在分数部分,我们有:1.01平均值:0*2^-1+1*2^-2
数字1(1.01的第一个位置)不需要保存,因为当以这种方式显示浮点数时,第一个数字始终是1。例如,转换:0.11=>1.1*2^(-1),0.01=>1*2^(-2)。
另一个示例显示始终删除第一个零:0.1将显示1*2^(-1)。所以第一个总是1。当前1*2^(-1)的数字为:
- 0:正数
- 127-1=126=0111110
- 分数:000000000000000000000(23个数字)
最后:原始二进制文件是:0 0111110亿
请在此处查看:http://www.binaryconvert.com/result_float.html?小数=048046053
现在,如果您已经了解如何保存浮点数。如果数字不能以32位(简单的精度)保存,会发生什么情况?
例如:十进制。1/3=0.33333333333333333333333333,因为它是无限的,我想我们有5位来保存数据。再说一遍,这不是真的。试想一下。因此,保存在计算机中的数据将是:
1 | 0.33333. |
现在,当计算机再次加载数字时,计算:
1 | 0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 + 3*10^-5. |
关于这一点:
1 2 3 | $a = '35'; $b = '-34.99'; echo ($a + $b); |
结果是0.01(十进制)。现在让我们用二进制显示这个数字。
1 | 0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary) |
请访问:http://www.binaryconvert.com/result_double.html?小数=048046048049
因为(01011100000100001111)的重复次数和1/3一样。所以计算机无法将这个号码保存在内存中。它必须牺牲。这导致了计算机的不精确。
先进的(你必须有数学知识)所以为什么我们可以很容易地用十进制而不是二进制显示0.01。
假设二进制0.01(十进制)的分数是有限的。
1 2 3 4 | 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. |
我的php返回0.01…
也许它与PHP版本有关(我使用5.2)
因为0.01不能精确地表示为二元分数系列的和。浮点数就是这样存储在内存中的。
我想这不是你想听到的,而是问题的答案。有关如何修复的信息,请参阅其他答案。
使用