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):
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:
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. Double dval (double precision) is specified by IEEE 754, and it must be 64bits.
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):
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:
<?php $a = 9223372036854775807; //The maximum value of 64-bit signed number$b = 9223372036854775808; //Maximum value +1var_dump($a); var_dump($b);
Output:
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:
<?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:
<?php echo PHP_INT_MAX; ?>
Of course, to be on the safe side, we should use strings to hold large integers and use mathematical function libraries such as bcmath to perform calculations.
In addition, there is another key configuration that will confuse us. 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:
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:
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.
Another point about floating point numbers is the answer to the following common questions:
<?php $f = 0.58; var_dump(intval($f * 100)); //Why output 57?>
Why is the output 57? Is there a bug in PHP?
I believe many classmates have had such questions, because there are many people who just ask me similar questions, not to mention that people often ask...
To understand this reason, we must first know the representation of floating point numbers (IEEE 754):
Floating point numbers, taking the length of 64 bits (double precision) as an example, will be represented by 1-bit sign bits (E), 11-bit exponent bits (Q), and 52-bit mantissa (M) (a total of 64 bits).
Sign bit: The highest bit indicates positive and negative of the data, 0 indicates positive number, and 1 indicates negative number.
Exponent bits: indicates that the data is powered at the base 2, and the exponent is represented by offset code.
Mantissa: A valid number after the decimal point of the data.
The key point here is that the decimals are represented in binary. You can use Baidu to describe how decimals are represented in binary. I will not repeat them here. The key thing we need to understand is that 0.58 is an infinite value for binary representation (the number below eliminates the implicit 1).
The binary representation of 0.58 is basically (52 bits): 0010100011110101110000101000111101011101100001010001111
The binary representation of 0.57 is basically (52 bits): 00100011110101110000101000111101011101011000101000111101
And if the binary of the two is only calculated by these 52 bits, they are:
0.58 -> 0.57999999999999996
0.57 -> 0.56999999999999995
As for the specific floating point multiplication of 0.58 * 100, we don’t consider it so detailed. If you are interested, you can look at it (Floating point). Let’s look at it vaguely based on mental arithmetic... 0.58 * 100 = 57.999999999
Then if you intvalve, it will naturally be 57...
It can be seen that the key point of this problem is: "You seem to have finite decimals, but they are infinite in the binary representation of a computer."
so, don't think that this is a PHP bug anymore, this is what...