PHP - 浮点数精度

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3726721/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 10:54:19  来源:igfitidea点击:

PHP - Floating Number Precision

phpfloating-pointprecisionfloating-accuracy

提问by dcmoody

$a = '35';
$b = '-34.99';
echo ($a + $b);

Results in 0.009999999999998

结果为 0.009999999999998

What is up with that? I wondered why my program kept reporting odd results.

这是怎么回事?我想知道为什么我的程序不断报告奇怪的结果。

Why doesn't PHP return the expected 0.01?

为什么 PHP 不返回预期的 0.01?

回答by NullUserException

Because floating point arithmetic != real number arithmetic. An illustration of the difference due to imprecision is, for some floats aand b, (a+b)-b != a. This applies to any language using floats.

因为浮点运算!= 实数运算。由于不精确而导致的差异的一个例子是,对于一些浮点数ab, (a+b)-b != a。这适用于任何使用浮动的语言。

Since floating pointare binary numbers with finite precision, there's a finite amount of representable numbers, which leads accuracy problemsand surprises like this. Here's another interesting read: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

由于浮点数是精度有限的二进制数,因此可表示的数是有限的,这会导致精度问题和类似的意外。这是另一个有趣的读物:每个计算机科学家都应该了解浮点运算



Back to your problem, basically there is no way to accurately represent 34.99 or 0.01 in binary (just like in decimal, 1/3 = 0.3333...), so approximations are used instead. To get around the problem, you can:

回到你的问题,基本上没有办法用二进制准确表示 34.99 或 0.01(就像十进制一样,1/3 = 0.3333 ...),所以使用近似值。要解决此问题,您可以:

  1. Use round($result, 2)on the result to round it to 2 decimal places.

  2. Use integers. If that's currency, say US dollars, then store $35.00 as 3500 and $34.99 as 3499, then divide the result by 100.

  1. round($result, 2)对结果使用将其四舍五入到小数点后两位。

  2. 使用整数。如果是货币,例如美元,则将 35.00 美元存储为 3500,将 34.99 美元存储为 3499,然后将结果除以 100。

It's a pity that PHP doesn't have a decimal datatype like otherlanguagesdo.

遗憾的是,PHP 不像其他语言那样具有十进制数据类型。

回答by stevendesu

Floating point numbers, like all numbers, must be stored in memory as a string of 0's and 1's. It's all bits to the computer. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them.

与所有数字一样,浮点数必须以 0 和 1 的字符串形式存储在内存中。这对计算机来说都是一点点。浮点数与整数的不同之处在于我们在查看 0 和 1 时如何解释它们。

One bit is the "sign" (0 = positive, 1 = negative), 8 bits are the exponent (ranging from -128 to +127), 23 bits are the number known as the "mantissa" (fraction). So the binary representation of (S1)(P8)(M23) has the value (-1^S)M*2^P

一位是“符号”(0 = 正,1 = 负),8 位是指数(范围从 -128 到 +127),23 位是称为“尾数”(分数)的数字。所以 (S1)(P8)(M23) 的二进制表示具有值 (-1^S)M*2^P

The "mantissa" takes on a special form. In normal scientific notation we display the "one's place" along with the fraction. For instance:

“尾数”采用特殊形式。在正常的科学记数法中,我们将“一个位置”与分数一起显示。例如:

4.39 x 10^2 = 439

4.39 x 10^2 = 439

In binary the "one's place" is a single bit. Since we ignore all the left-most 0's in scientific notation (we ignore any insignificant figures) the first bit is guaranteed to be a 1

在二进制中,“一个位置”是一位。由于我们忽略了科学记数法中所有最左边的 0(我们忽略了任何无关紧要的数字),因此第一位保证为 1

1.101 x 2^3 = 1101 = 13

1.101 x 2^3 = 1101 = 13

Since we are guaranteed that the first bit will be a 1, we remove this bit when storing the number to save space. So the above number is stored as just 101 (for the mantissa). The leading 1 is assumed

由于我们保证第一位为 1,因此我们在存储数字时删除该位以节省空间。因此,上述数字仅存储为 101(对于尾数)。假设前导 1

As an example, let's take the binary string

例如,让我们以二进制字符串为例

00000010010110000000000000000000

Breaking it into it's components:

把它分解成它的组成部分:

Sign    Power           Mantissa
 0     00000100   10110000000000000000000
 +        +4             1.1011
 +        +4       1 + .5 + .125 + .0625
 +        +4             1.6875

Applying our simple formula:

应用我们的简单公式:

(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27

In other words, 00000010010110000000000000000000 is 27 in floating point (according to IEEE-754 standards).

换句话说,00000010010110000000000000000000 是 27 的浮点数(根据 IEEE-754 标准)。

For many numbers there is no exact binary representation, however. Much like how 1/3 = 0.333.... repeating forever, 1/100 is 0.00000010100011110101110000..... with a repeating "10100011110101110000". A 32-bit computer can't store the entire number in floating point, however. So it makes its best guess.

然而,对于许多数字,没有精确的二进制表示。就像 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

(note that negative 7 is produced using 2's complement)

(请注意,负 7 是使用 2 的补码产生的)

It should be immediately clear that 01111100101000111101011100001010 looks nothing like 0.01

应该立即清楚 01111100101000111101011100001010 看起来一点也不像 0.01

More importantly, however, this contains a truncated version of a repeating decimal. The original decimal contained a repeating "10100011110101110000". We've simplified this to 01000111101011100001010

然而,更重要的是,这包含重复小数的截断版本。原始小数包含重复的“10100011110101110000”。我们已将其简化为 01000111101011100001010

Translating this floating point number back into decimal via our formula we get 0.0099999979 (note that this is for a 32-bit computer. A 64-bit computer would have much more accuracy)

通过我们的公式将此浮点数转换回十进制,我们得到 0.0099999979(请注意,这是针对 32 位计算机的。64 位计算机的精度会更高)

A Decimal Equivalent

十进制等价物

If it helps to understand the problem better, let's look decimal scientific notation when dealing with repeating decimals.

如果它有助于更​​好地理解问题,让我们在处理重复小数时看看十进制科学记数法。

Let's assume that we have 10 "boxes" to store digits. Therefore if we wanted to store a number like 1/16 we would write:

假设我们有 10 个“盒子”来存储数字。因此,如果我们想存储一个像 1/16 这样的数字,我们可以这样写:

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+

Which is clearly just 6.25 e -2, where eis shorthand for *10^(. We've allocated 4 boxes for the decimal even though we only needed 2 (padding with zeroes), and we've allocated 2 boxes for signs (one for the sign of the number, one of the sign of the exponent)

这显然只是6.25 e -2, wheree是 的简写*10^(。即使我们只需要 2 个(用零填充),我们也为小数分配了 4 个框,并且我们为符号分配了 2 个框(一个用于数字的符号,一个用于指数的符号)

Using 10 boxes like this we can display numbers ranging from -9.9999 e -9to +9.9999 e +9

使用 10 个这样的框,我们可以显示从-9.9999 e -9+9.9999 e +9

This works fine for anything with 4 or fewer decimal places, but what happens when we try to store a number like 2/3?

这适用于小数点后 4 位或更少的任何内容,但是当我们尝试存储一个数字时会发生什么2/3

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

This new number 0.66667does not exactly equal 2/3. In fact, it's off by 0.000003333.... If we were to try and write 0.66667in base 3, we would get 0.2000000000012...instead of 0.2

这个新数字0.66667并不完全相等2/3。事实上,它是关闭的0.000003333...。如果我们尝试0.66667以基数 3编写,我们将得到0.2000000000012...而不是0.2

This problem may become more apparent if we take something with a larger repeating decimal, like 1/7. This has 6 repeating digits: 0.142857142857...

如果我们采用更大的重复小数点,例如1/7. 这有 6 个重复数字:0.142857142857...

Storing this into our decimal computer we can only show 5 of these digits:

将其存储到我们的十进制计算机中,我们只能显示其中的 5 个数字:

+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

This number, 0.14286, is off by .000002857...

这个数字, 0.14286, 关闭了.000002857...

It's "close to correct", but it's not exactly correct, and so if we tried to write this number in base 7 we would get some hideous number instead of 0.1. In fact, plugging this into Wolfram Alpha we get: .10000022320335...

它“接近正确”,但并不完全正确,因此如果我们尝试以 7 为基数写出这个数字,我们会得到一些可怕的数字而不是0.1。事实上,将其插入 Wolfram Alpha 我们得到:.10000022320335...

These minor fractional differences should look familiar to your 0.0099999979(as opposed to 0.01)

这些细微的差异对您来说应该很熟悉0.0099999979(而不是0.01

回答by ircmaxell

There's plenty of answers here about why floating point numbers work the way they do...

这里有很多关于为什么浮点数以它们的方式工作的答案......

But there's little talk of arbitrary precision (Pickle mentioned it). If you want (or need) exact precision, the only way to do it (for rational numbers at least) is to use the BC Mathextension (which is really just a BigNum, Arbitrary Precisionimplementation...

但是几乎没有谈论任意精度(Pickle 提到过)。如果你想要(或需要)精确的精度,唯一的方法(至少对于有理数)是使用BC Math扩展(这实际上只是一个BigNum,任意精度的实现......

To add two numbers:

将两个数字相加:

$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);

will result in 12345678901235.1234567890...

会导致12345678901235.1234567890...

This is called arbitrary precision math. Basically all numbers are strings which are parsed for every operation and operations are performed on a digit by digit basis (think long division, but done by the library). So that means it's quite slow (in comparison to regular math constructs). But it's very powerful. You can multiply, add, subtract, divide, find modulo and exponentiate any number that has an exact string representation.

这称为任意精度数学。基本上所有数字都是为每个操作解析的字符串,并且操作是逐位执行的(想想长除法,但由库完成)。所以这意味着它很慢(与常规数学结构相比)。但它非常强大。您可以对任何具有精确字符串表示的数字进行乘、加、减、除、求模和取幂。

So you can't do 1/3with 100% accuracy, since it has a repeating decimal (and hence isn't rational).

所以你不能做到1/3100% 准确,因为它有一个重复的小数(因此是不合理的)。

But, if you want to know what 1500.0015squared is:

但是,如果您想知道1500.0015平方是什么:

Using 32 bit floats (double precision) gives the estimated result of:

使用 32 位浮点数(双精度)给出的估计结果为:

2250004.5000023

But bcmath gives the exact answer of:

但是 bcmath 给出了确切的答案:

2250004.50000225

It all depends on the precision you need.

这一切都取决于您需要的精度。

Also, something else to note here. PHP can only represent either 32 bit or 64 bit integers (depending on your install). So if an integer exceeds the size of the native int type (2.1 billion for 32bit, 9.2 x10^18, or 9.2 billion billion for signed ints), PHP will convert the int into a float. While that's not immediately a problem (Since all ints smaller than the precision of the system's float are by definition directly representable as floats), if you try multiplying two together, it'll lose significant precision.

另外,这里还有一点需要注意。PHP 只能表示 32 位或 64 位整数(取决于您的安装)。因此,如果整数超过本机 int 类型的大小(32 位为 21 亿,9.2 x10^18,或有符号整数为 92 亿),PHP 会将 int 转换为浮点数。虽然这不是一个立即的问题(因为所有小于系统浮点精度的整数根据定义都可以直接表示为浮点数),但如果您尝试将两个相乘,它将失去显着的精度。

For example, given $n = '40000000002':

例如,给定$n = '40000000002'

As a number, $nwill be float(40000000002), which is fine since it's exactly represented. But if we square it, we get: float(1.60000000016E+21)

作为一个数字,$nwill be float(40000000002),这很好,因为它被精确表示。但是如果我们把它平方,我们得到:float(1.60000000016E+21)

As a string (using BC math), $nwill be exactly '40000000002'. And if we square it, we get: string(22) "1600000000160000000004"...

作为字符串(使用 BC 数学),$n将正好是'40000000002'. 如果我们把它平方,我们得到:string(22) "1600000000160000000004"...

So if you need the precision with large numbers, or rational decimal points, you might want to look into bcmath...

因此,如果您需要大数或有理小数点的精度,则可能需要查看 bcmath ...

回答by Quasipickle

bcadd()might be useful here.

bcadd()在这里可能有用。

<?PHP

$a = '35';
$b = '-34.99';

echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);

?>

(inefficient output for clarity)

(为了清晰起见,输出效率低下)

First line gives me 0.009999999999998. Second gives me 0.01

第一行给了我 0.009999999999998。第二个给了我 0.01

回答by Tomasz Kowalczyk

Use PHP's round()function: http://php.net/manual/en/function.round.php

使用 PHP 的round()函数:http: //php.net/manual/en/function.round.php

This answer solves problem, but not explains why. I thought that it is obvious [I am also programming in C++, so it IS obvious for me ;]], but if not, let's say that PHP has it's own calculating precision and in that particular situation it returned most complying information regarding that calculation.

这个答案解决了问题,但没有解释原因。我认为这很明显 [我也在用 C++ 编程,所以这对我来说很明显;]],但如果没有,假设 PHP 有它自己的计算精度,在这种特殊情况下,它返回了最符合该计算的信息.

回答by Andrey

Because 0.01 can't be represented exactly as sum of series of binary fractions. And that is how floats are stored in memory.

因为 0.01 不能完全表示为二进制分数系列的总和。这就是浮点数在内存中的存储方式。

I guess it is not what you want to hear, but it is answer to question. For how to fix see other answers.

我想这不是您想听到的,而是对问题的回答。有关如何修复,请参阅其他答案。

回答by christian Nguyen

Every number will be save in computer by binary value such as 0, 1. In Single-precision numbers occupy 32 bits.

每个数字都会以二进制值(例如 0、1)保存在计算机中。在单精度数中,占 32 位。

The floating point number can be presented by: 1 bit for sign, 8 bit for exponent and 23 bit called mantissa (fraction).

浮点数可以表示为:1 位表示符号,8 位表示指数,23 位称为尾数(分数)。

Look the example below:

看下面的例子:

0.15625 = 0.00101 = 1.01*2^(-3)

0.15625 = 0.00101 = 1.01*2^(-3)

enter image description here

在此处输入图片说明

  • sign: 0 mean positive number, 1 mean negative number, in this case it is 0.

  • exponent: 01111100 = 127 - 3 = 124.

    Note: the bias = 127 so biased exponent = ?3 + the "bias". In single precision, the bias is ,127, so in this example the biased exponent is 124;

  • At fraction part, we have: 1.01 mean: 0*2^-1 + 1*2^-2

    Number 1 (first position of 1.01) do not need to save because when present the floating number in this way the first number always be 1. For example convert: 0.11 => 1.1*2^(-1), 0.01 => 1*2^(-2).

  • 符号: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 => 1.1*2^(-1), 0.01 => 1* 2^(-2)。

Another example show always remove the first zero: 0.1 will be presented 1*2^(-1). So the first alwasy be 1. The present number of 1*2^(-1) will be:

另一个例子显示总是删除第一个零:0.1 将呈现 1*2^(-1)。所以第一个总是 1。 1*2^(-1) 的当前数将是:

  • 0: positive number
  • 127-1 = 126 = 01111110
  • fraction: 00000000000000000000000 (23 number)
  • 0:正数
  • 127-1 = 126 = 01111110
  • 分数:00000000000000000000000(23个数字)

Finally: The raw binary is: 0 01111110 00000000000000000000000

最后:原始二进制是:0 01111110 00000000000000000000000

Check it here: http://www.binaryconvert.com/result_float.html?decimal=048046053

在这里检查:http: //www.binaryconvert.com/result_float.html?decimal=048046053

Now if you already understand how a floating point number are saved. What happen if the number cannot save in 32 bit (simple precision).

现在,如果您已经了解如何保存浮点数。如果数字不能以 32 位(简单精度)保存会发生什么。

For example: in decimal. 1/3 = 0.3333333333333333333333 and because it is infinite I suppose we have 5 bit to save data. Repeat again this is not real. just suppose. So the data saved in computer will be:

例如:十进制。1/3 = 0.3333333333333333333333 并且因为它是无限的,我想我们有 5 位来保存数据。再重复一遍,这不是真的。只是假设。所以计算机中保存的数据将是:

0.33333.

Now when the number loaded the computer calculate again:

现在当数字加载计算机时再次计算:

0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.

About this:

对这个:

$a = '35';
$b = '-34.99';
echo ($a + $b);

The result is 0.01 ( decimal). Now let show this number in binary.

结果是 0.01(十进制)。现在让我们用二进制显示这个数字。

0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)

Check here: http://www.binaryconvert.com/result_double.html?decimal=048046048049

在这里查看:http: //www.binaryconvert.com/result_double.html?decimal=048046048049

Because (01011100001010001111) is repeat just like 1/3. So computer cannot save this number in their memory. It must sacrifice. This lead not accuracy in computer.

因为 (01011100001010001111) 就像 1/3 一样重复。所以计算机无法将这个数字保存在它们的内存中。它必须牺牲。这导致计算机不准确。

Advanced( You must have knowledge about mathematics ) So why we can easily show 0.01 in decimal but not in binary.

高级(您必须具备数学知识) 那么为什么我们可以轻松地以十进制显示 0.01 而不能以二进制显示。

Suppose the fraction in binary of 0.01 (decimal) is finite.

假设二进制分数 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.

回答by Jurijs Nesterovs

wouldn't it be easier to use number_format(0.009999999999998, 2)or $res = $a+$b; -> number_format($res, 2);?

会不会更容易使用number_format(0.009999999999998, 2)$res = $a+$b; -> number_format($res, 2);