PHP 8.4.1 Released!

Float 浮点型

浮点型(也叫浮点数 float,双精度数 double 或实数 real)可以用以下任一语法定义:

<?php
$a
= 1.234;
$b = 1.2e3;
$c = 7E-10;
$d = 1_234.567; // 从 PHP 7.4.0 开始支持
?>

浮点数的形式表示(PHP 7.4.0 之前不支持下划线):

LNUM          [0-9]+(_[0-9]+)*
DNUM ({LNUM}?"."{LNUM}) | ({LNUM}"."{LNUM}?)
EXPONENT_DNUM (({LNUM} | {DNUM}) [eE][+-]? {LNUM})

浮点数的字长和平台相关,尽管通常最大值是 1.8e308 并具有 14 位十进制数字的精度(64 位 IEEE 格式)。

警告

浮点数的精度

浮点数的精度有限。尽管取决于系统,PHP 通常使用 IEEE 754 双精度格式,则由于取整而导致的最大相对误差为 1.11e-16。非基本数学运算可能会给出更大误差,并且要考虑到进行复合运算时的误差传递。

此外,以十进制能够精确表示的有理数如 0.10.7,无论有多少尾数都不能被内部所使用的二进制精确表示,因此不能在不丢失一点点精度的情况下转换为二进制的格式。这就会造成混乱的结果:例如,floor((0.1+0.7)*10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118...

所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数或者 gmp 函数

参见» 浮点数指南网页的简单解释。

转换为浮点数

从 string 转换

如果 string 是 numeric 或者前导数字, 则将它解析为相应的 float 值,否则将转换为零(0)。

从其他类型转换

对于其它类型的值,其情况类似于先将值转换成 int,然后再转换成 float。 请参阅“转换为整型”一节以获取更多信息。

注意:

某些类型在转换成 int 时有未定义行为,转换为 float 时也会如此。

比较浮点数

如上述警告信息所言,由于内部表达方式的原因,比较两个浮点数是否相等是有问题的。不过还是有迂回的方法来比较浮点数值的。

要测试浮点数是否相等,要使用一个仅比该数值大一丁点的最小误差值。该值也被称为机器极小值(epsilon)或最小单元取整数,是计算中所能接受的最小的差别值。

$a$b 在小数点后五位精度内都是相等的。

<?php
$a
= 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;

if(
abs($a-$b) < $epsilon) {
echo
"true";
}
?>

NaN

某些数学运算会产生一个由常量 NAN 所代表的结果。此结果代表着一个在浮点数运算中未定义或不可表述的值。任何拿此值与其它任何值(除了 true)进行的松散或严格比较的结果都是 false

由于 NAN 代表着任何不同值,不应拿 NAN 去和其它值进行比较,包括其自身,应该用 is_nan() 来检查。

添加备注

用户贡献的备注 11 notes

up
261
catalin dot luntraru at gmail dot com
11 years ago
$x = 8 - 6.4; // which is equal to 1.6
$y = 1.6;
var_dump($x == $y); // is not true

PHP thinks that 1.6 (coming from a difference) is not equal to 1.6. To make it work, use round()

var_dump(round($x, 2) == round($y, 2)); // this is true

This happens probably because $x is not really 1.6, but 1.599999.. and var_dump shows it to you as being 1.6.
up
121
feline at NOSPAM dot penguin dot servehttp dot com
20 years ago
General computing hint: If you're keeping track of money, do yourself and your users the favor of handling everything internally in cents and do as much math as you can in integers. Store values in cents if at all possible. Add and subtract in cents. At every operation that wii involve floats, ask yourself "what will happen in the real world if I get a fraction of a cent here" and if the answer is that this operation will generate a transaction in integer cents, do not try to carry fictional fractional accuracy that will only screw things up later.
up
66
www.sarioz.com
21 years ago
just a comment on something the "Floating point precision" inset, which goes: "This is related to .... 0.3333333."

While the author probably knows what they are talking about, this loss of precision has nothing to do with decimal notation, it has to do with representation as a floating-point binary in a finite register, such as while 0.8 terminates in decimal, it is the repeating 0.110011001100... in binary, which is truncated. 0.1 and 0.7 are also non-terminating in binary, so they are also truncated, and the sum of these truncated numbers does not add up to the truncated binary representation of 0.8 (which is why (floor)(0.8*10) yields a different, more intuitive, result). However, since 2 is a factor of 10, any number that terminates in binary also terminates in decimal.
up
4
251701981 at qq dot com
1 year ago
<?php

//Please consider the following code
printf("%.53f\n",0.7+0.1); // 0.79999999999999993338661852249060757458209991455078125

var_dump(0.7+0.1); // float(0.8)

var_dump(0.799999999999999); //float(0.8)

var_dump(0.7999999); // float(0.7999999)

//Conclusion: PHP can support up to 53 decimal places, but in some output functions such as var_ Dump, when outputting decimals exceeding 14 places, will round off the 15th place, which causes significant misleading
//experimental environment:linux x64,php7.2.x
up
23
backov at spotbrokers-nospamplz dot com
21 years ago
I'd like to point out a "feature" of PHP's floating point support that isn't made clear anywhere here, and was driving me insane.

This test (where var_dump says that $a=0.1 and $b=0.1)

if ($a>=$b) echo "blah!";

Will fail in some cases due to hidden precision (standard C problem, that PHP docs make no mention of, so I assumed they had gotten rid of it). I should point out that I originally thought this was an issue with the floats being stored as strings, so I forced them to be floats and they still didn't get evaluated properly (probably 2 different problems there).

To fix, I had to do this horrible kludge (the equivelant of anyway):

if (round($a,3)>=round($b,3)) echo "blah!";

THIS works. Obviously even though var_dump says the variables are identical, and they SHOULD BE identical (started at 0.01 and added 0.001 repeatedly), they're not. There's some hidden precision there that was making me tear my hair out. Perhaps this should be added to the documentation?
up
5
lwiwala at gmail dot com
7 years ago
To compare two numbers use:

$epsilon = 1e-6;

if(abs($firstNumber-$secondNumber) < $epsilon){
// equals
}
up
9
Luzian
19 years ago
Be careful when using float values in strings that are used as code later, for example when generating JavaScript code or SQL statements. The float is actually formatted according to the browser's locale setting, which means that "0.23" will result in "0,23". Imagine something like this:

$x = 0.23;
$js = "var foo = doBar($x);";
print $js;

This would result in a different result for users with some locales. On most systems, this would print:

var foo = doBar(0.23);

but when for example a user from Germany arrives, it would be different:

var foo = doBar(0,23);

which is obviously a different call to the function. JavaScript won't state an error, additional arguments are discarded without notice, but the function doBar(a) would get 0 as parameter. Similar problems could arise anywhere else (SQL, any string used as code somewhere else). The problem persists, if you use the "." operator instead of evaluating the variable in the string.

So if you REALLY need to be sure to have the string correctly formatted, use number_format() to do it!
up
6
james dot cridland at virginradio dot co dot uk
21 years ago
The 'floating point precision' box in practice means:

<? echo (69.1-floor(69.1)); ?>
Think this'll return 0.1?
It doesn't - it returns 0.099999999999994

<? echo round((69.1-floor(69.1))); ?>
This returns 0.1 and is the workaround we use.

Note that
<? echo (4.1-floor(4.1)); ?>
*does* return 0.1 - so if you, like us, test this with low numbers, you won't, like us, understand why all of a sudden your script stops working, until you spend a lot of time, like us, debugging it.

So, that's all lovely then.
up
6
magicaltux at php dot net
14 years ago
In some cases you may want to get the maximum value for a float without getting "INF".

var_dump(1.8e308); will usually show: float(INF)

I wrote a tiny function that will iterate in order to find the biggest non-infinite float value. It comes with a configurable multiplicator and affine values so you can share more CPU to get a more accurate estimate.

I haven't seen better values with more affine, but well, the possibility is here so if you really thing it's worth the cpu time, just try to affine more.

Best results seems to be with mul=2/affine=1. You can play with the values and see what you get. The good thing is this method will work on any system.

<?php
function float_max($mul = 2, $affine = 1) {
$max = 1; $omax = 0;
while((string)
$max != 'INF') { $omax = $max; $max *= $mul; }

for(
$i = 0; $i < $affine; $i++) {
$pmax = 1; $max = $omax;
while((string)
$max != 'INF') {
$omax = $max;
$max += $pmax;
$pmax *= $mul;
}
}
return
$omax;
}
?>
up
5
zelko at mojeime dot com
13 years ago
<?php
$binarydata32
= pack('H*','00000000');
$float32 = unpack("f", $binarydata32); // 0.0

$binarydata64 = pack('H*','0000000000000000');
$float64 = unpack("d", $binarydata64); // 0.0
?>

I get 0 both for 32-bit and 64-bit numbers.

But, please don't use your own "functions" to "convert" from float to binary and vice versa. Looping performance in PHP is horrible. Using pack/unpack you use processor's encoding, which is always correct. In C++ you can access the same 32/64 data as either float/double or 32/64 bit integer. No "conversions".

To get binary encoding:
<?php
$float32
= pack("f", 5300231);
$binarydata32 =unpack('H*',$float32); //"0EC0A14A"

$float64 = pack("d", 5300231);
$binarydata64 =unpack('H*',$float64); //"000000C001385441"
?>

And my example from half a year ago:
<?php
$binarydata32
= pack('H*','0EC0A14A');
$float32 = unpack("f", $binarydata32); // 5300231

$binarydata64 = pack('H*','000000C001385441');
$float64 = unpack("d", $binarydata64); // 5300231
?>

And please mind the Big and Little endian boys...
up
3
rick at ninjafoo dot com
19 years ago
Concider the following:

(19.6*100) != 1960

echo gettype(19.6*100) returns 'double', However even .....

(19.6*100) !== (double)1960

19.6*100 cannot be compaired to anything without manually
casting it as something else first.

(string)(19.6*100) == 1960

Rule of thumb, if it has a decimal point, use the BCMath functions.
To Top