SoFunction
Updated on 2025-03-10

You should know about PHP floating point numbers

PHP is a weak-type language. Such characteristics must require seamless and transparent implicit type conversion. PHP uses zval to save any type of numerical values. The structure of zval is as follows (5.2 as an example):

Copy the codeThe code is as follows:

struct _zval_struct {
    /* Variable information */
    zvalue_value value;     /* value */
    zend_uint refcount;
    zend_uchar type;    /* active type */
    zend_uchar is_ref;
};

In the above structure, the zvalue_value union that actually stores the value itself is:
Copy the codeThe code is as follows:

typedef union _zvalue_value {
    long lval;                  /* long value */
    double dval;                /* double value */
    struct {
        char *val;
        int len;
    } str;
    HashTable *ht;              /* hash table value */
    zend_object_value obj;
} zvalue_value;

Today's topic, we only focus on two members, lval and dval. We need to realize that long lval is different from the word length of the compiler and OS. It may be 32bits or 64bits, and double dval (double precision) isIEEE 754The regulations are fixed lengths and must be 64 bits.

Please remember this, which creates the "non-platform irrelevance" of some PHP code. Our next discussion, except specifically specified, assumes that long is 64bits

I will not quote the floating point counting method of IEEE 754 here. If you are interested, you can check it yourself. The key point is that the mantissa of the double is saved using a 52-bit bit. If you count the hidden 1-bit significant bit, there are a total of 53 bits.

Here, a very interesting question is introduced. Let's use the c code as an example (assuming that long is 64bits):

Copy the codeThe code is as follows:

    long a = x;
    assert(a == (long)(double)a);

May I ask, when the value of a is within what range, can the above code assert success? (Leave it at the end of the article)

Now let's get back to the topic. Before PHP executes a script, it first needs to read the script and analyze the script. This process also includes zvaluating the literals in the script, such as for the following script:

Copy the codeThe code is as follows:

<?php
$a = 9223372036854775807; //The maximum value of 64-bit signed number
$b = 9223372036854775808; //Maximum value +1
var_dump($a);
var_dump($b);

Output:
Copy the codeThe code is as follows:

int(9223372036854775807)
float(9.22337203685E+18)

In other words, in the lexical analysis stage, PHP will judge whether the value of a literal value exceeds the current system's long table value range. If not, lval is used to save it. zval is IS_LONG, otherwise it is represented by dval, zval IS_FLOAT.

We must be careful about any value greater than the maximum integer value, because it may have accuracy losses:

Copy the codeThe code is as follows:

<?php
$a = 9223372036854775807;
$b = 9223372036854775808;
 
var_dump($a === ($b - 1));

The output is false.

Now let’s continue with the discussion at the beginning. As mentioned before, PHP integers may be 32-bit or 64-bit. This determines that some codes that can run normally on 64-bit may lose accuracy due to invisible type conversion, resulting in the code not being able to run normally on a 32-bit system.

Therefore, we must be alert to this critical value, fortunately, this critical value has been defined in PHP:

Copy the codeThe code is as follows:

<?php
    echo PHP_INT_MAX;
 ?>
 

Of course, to be on the safe side, we should use strings to hold large integers, and use, for examplebcmathSuch a mathematical function library is used to perform calculations.

In addition, there is another key configuration that will confuse us, this configuration is, This configuration determines how many valid bits will be output when PHP outputs another float value.

Finally, let’s look back at the question raised above, which is a long integer, what is the maximum value, so that we can ensure that after returning to float and then back to long, there will be no accuracy loss?

For example, for integers, we know that its binary representation is, 101, now, let's move the right two bits to 1.01, discarding the implicit significant bit 1 of the high bit, and we get the binary value storing 5 in the double as:

Copy the codeThe code is as follows:

0/*Symbol bit*/ 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

The binary representation of 5 is stored in the mantissa part without loss at all. In this case, if you transfer from double back to long, no accuracy loss will occur.

We know that double uses 52 bits to represent the mantissa, and counts the implicit first bit 1, which is a total of 53 bits of precision... Then we can also find that if a long integer has a value less than:

Copy the codeThe code is as follows:

2^53 - 1 == 9007199254740991; //Remember, we now assume that it is a long of 64bits

Then, this integer will not lose accuracy when a numerical conversion of long->double->long occurs.