Hướng dẫn dùng digit numeric trong PHP

Floating point numbers [also known as "floats", "doubles", or "real numbers"] can be specified using any of the following syntaxes:

Formally as of PHP 7.4.0 [previously, underscores have not been allowed]:

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

The size of a float is platform-dependent, although a maximum of approximately 1.8e308 with a precision of roughly 14 decimal digits is a common value [the 64 bit IEEE format].

Warning

Floating point precision

Floating point numbers have limited precision. Although it depends on the system, PHP typically uses the IEEE 754 double precision format, which will give a maximum relative error due to rounding in the order of 1.11e-16. Non elementary arithmetic operations may give larger errors, and, of course, error propagation must be considered when several operations are compounded.

Additionally, rational numbers that are exactly representable as floating point numbers in base 10, like 0.1 or 0.7, do not have an exact representation as floating point numbers in base 2, which is used internally, no matter the size of the mantissa. Hence, they cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor[[0.1+0.7]*10] will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9999999999999991118....

So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available.

For a "simple" explanation, see the » floating point guide that's also titled "Why don’t my numbers add up?"

Converting to float

From strings

If the string is numeric or leading numeric then it will resolve to the corresponding float value, otherwise it is converted to zero [0].

From other types

For values of other types, the conversion is performed by converting the value to int first and then to float. See Converting to integer for more information.

Note:

As certain types have undefined behavior when converting to int, this is also the case when converting to float.

Comparing floats

As noted in the warning above, testing floating point values for equality is problematic, due to the way that they are represented internally. However, there are ways to make comparisons of floating point values that work around these limitations.

To test floating point values for equality, an upper bound on the relative error due to rounding is used. This value is known as the machine epsilon, or unit roundoff, and is the smallest acceptable difference in calculations.

$a and $b are equal to 5 digits of precision.

NaN

Some numeric operations can result in a value represented by the constant NAN. This result represents an undefined or unrepresentable value in floating-point calculations. Any loose or strict comparisons of this value against any other value, including itself, but except true, will have a result of false.

Because NAN represents any number of different values, NAN should not be compared to other values, including itself, and instead should be checked for using is_nan[].

catalin dot luntraru at gmail dot com

8 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.

feline at NOSPAM dot penguin dot servehttp dot com

18 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.

www.sarioz.com

19 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.

yobberowich at gmail dot com

5 years ago

@catalyn

You write about 8-6.4 being not equal to 1.6. The true reason is that absolute epsilon value depends on the exponent and that conversion to float rounds down to next possible value. 6.4 has a bigger rounding error than 1.6 and the resulting error from computing 8-6.4 is bigger than rounding error of 1.6 itself. [float]1.6 is the number closest to being 1.6 while [float][8-6.4] is bigger than 1.6 - conversion to float rounds down. So, 1.6 will be like 1.599999 and 8-6.4 will be somewhat like 1.600001

The solution would be to use decimal math for constant expressions.

backov at spotbrokers-nospamplz dot com

19 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?

james dot cridland at virginradio dot co dot uk

19 years ago

The 'floating point precision' box in practice means:


Think this'll return 0.1?
It doesn't - it returns 0.099999999999994


This returns 0.1 and is the workaround we use.

Note that

*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.

lwiwala at gmail dot com

5 years ago

To compare two numbers use:

$epsilon = 1e-6;

if[abs[$firstNumber-$secondNumber] < $epsilon]{
   // equals
}

jrf_php dot net at NO_SPAM dot adviesenzo dot nl

4 years ago

Just on the off-chance that someone is ever looking for it - here a regular expression which will work in PHP to recognize both floats and integers:



Will output:
string[7] "1263576"
string[5] "1.234"
string[5] "1.2e3"
string[5] "7E-10"
string[4] "1234"
string[4] "35E1"

Luzian

16 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!

magicaltux at php dot net

12 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.

rick at ninjafoo dot com

17 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.

zelko at mojeime dot com

11 years ago



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:


And my example from half a year ago:


And please mind the Big and Little endian boys...

pcunha at gmail dot com

9 years ago

To simply convert 32 bits float from hex to float:



This may be useful for arduino interface with php.

inforsci at gmail dot com

12 years ago

convert 32bit HEX values into IEEE 754 floating point

Anonymous

12 years ago

Calculations involving float types become inaccurate when it deals with numbers with more than approximately 8 digits long where ever the decimal point is.  This is because of how 32bit floats are commonly stored in memory.  This means if you rely on float types while working with tiny fractions or large numbers, your calculations can end up between tiny fractions to several trillion off.

This usually won't matter when converting to binary memory storage form and editing many applications' float memory addresses directly, or dealing with smaller length numbers.  But if you're working with larger scale numbers and decimals, it's best to switch to working with other types: //www.php.net/manual/en/refs.math.php

Adam H

7 years ago

I've just come across this issue with floats when writing a function for pricing. When converting from string to a float, with 2 digits of precision, the issue with comparing floats can pop up and give inconsistent results due to the conversion process.

An easier way rather than relying on the mentioned epsilon method is to use number_format [at least for me as I'll remember it!].

Example function that can return an unexpected result:

if[[float]$a == [float]$b] {
echo true;
} else {
echo false;
}

echo's false in this example.

Using number format here to trim down the precision [2 point precision being mostly used for currencies etc, although higher precisions should be correctly catered for by number_format], will return an expected result:

if[number_format[[float]$a, 2] == number_format[[float]$b, 2]] {
echo true;
} else {
echo false;
}

Correctly echo's true.

davidszilardd at gmail dot com

10 years ago

The function returns 5 for 5,000 because if there is no decimal point, then the first strpos will be FALSE, and FALSE < 1 is TRUE so the condition will be still true.

It should be checked whether strpos returns a valid position:

Julian L

11 years ago

Convert a hex string into a 32-bit IEEE 754 float number.  This function is 2 times faster then the below hex to 32bit function.  This function only changes datatypes [string to int] once. Also, this function is a port from the hex to 64bit function from below.



nathanb at php dot net

7 years ago

An extremely small and simple example of this is:

jack at surfacefinishtech dot com

9 years ago

Bài Viết Liên Quan

Chủ Đề