I know that type conversion may lead small errors due to the way floating numbers are stored in binary. Yet I do have the use case of casting a float into a string, yet now I have the issue that this may remove a char:
$we_all_float_down_here = (float) 3467.60009765625;
$expected_string = '3467.60009765625';
var_dump($expected_string === (string) $we_all_float_down_here);
echo "Last chars gets stripped:\n"
. (string) $we_all_float_down_here . "\n"
. $expected_string;
This outputs:
bool(false)
Last chars gets stripped:
3467.6000976562
3467.60009765625
How can I cast the float to a string without losing any digit in the process?
Note:
ini_set('precision', 100);won't fix that certain float values are stored unprecislynumber_formatmay work for some numbers just as my example, yet it too does not change the fact that other numbers will too be unprecise, or rather that some floats are identical to other different looking floats. (In decimal,.99999...is (mathematical-correctly) identical to1, even though it looks different. In php, due to binary representation and memory constraints, the floats for3467.6000976563and3467.60009765625318323146are (mathematically-incorrectly) identical here.)
2 Answers 2
Don't use floats. Use string / bcmath when precision is a requirement.
If you don't care about precision, you may circumvent the issue via (float) (string) castin:
$floats = [
3467.60009765625,
3467.6000976562,
];
var_dump(array_map(fn(float $float) => (float) $float, $floats));
var_dump(array_map(fn(float $float) => (float) (string) $float, $floats));
---
array(2) {
[0]=>
float(3467.60009765625)
[1]=>
float(3467.6000976562)
}
array(2) {
[0]=>
float(3467.6000976562)
[1]=>
float(3467.6000976562)
}
My confusion as to why I thought I was able to "get the float as is" stems from the fact that some floats may not be identical but their string representation is. Yet others floats are both identical in their float values and their string representation.
Given this code:
<?php
$float_pairs = [
[3467.60009765625, 3467.6000976562],
[3467.600097656253, 3467.60009765625318323146],
];
function floatToBinStr($value) {
$bin = '';
$packed = pack('d', $value); // use 'f' for 32 bit
foreach(str_split(strrev($packed)) as $char) {
$bin .= str_pad(decbin(ord($char)), 8, 0, STR_PAD_LEFT);
}
return $bin;
}
foreach ($float_pairs as $pair) {
$float_1 = $pair[0];
$float_2 = $pair[1];
$float_1_as_string = (string) $float_1;
$float_2_as_string = (string) $float_2;
$they_are_identical = $float_1 === $float_2;
$their_string_is_identical = (string) $float_1 === (string) $float_2;
$binary_1 = floatToBinStr($float_1);
$binary_2 = floatToBinStr($float_2);
echo '<pre>The floats are identical:', var_dump($they_are_identical), '</pre>';
echo '<pre>Their string are identical', var_dump($their_string_is_identical), '</pre>';
echo '<pre>', var_dump($float_1 ), '</pre>';
echo '<pre>', var_dump($float_2), '</pre>';
echo '<pre>', var_dump((string) $float_1), '</pre>';
echo '<pre>', var_dump((string) $float_2), '</pre>';
echo '<pre>', var_dump($binary_1), '</pre>';
echo '<pre>', var_dump($binary_2), '</pre>';
echo '<hr />';
}
die();
One gets the output:
The floats are identical: bool(false)
Their string are identical bool(true)
float(3467.60009765625)
float(3467.6000976562)
string(64) "0100000010101011000101110011001101000000000000000000000000000000"
string(64) "0100000010101011000101110011001100111111111111111111111110010010"
string(31) "3467.6000976562 3467.6000976562"
The floats are identical: bool(true)
Their string are identical bool(true)
float(3467.600097656253)
float(3467.600097656253)
string(64) "0100000010101011000101110011001101000000000000000000000000000111"
string(64) "0100000010101011000101110011001101000000000000000000000000000111"
string(31) "3467.6000976563 3467.6000976563"
This leads to the weird behavior that during a debug session one can see the specific correct float value of 3467.60009765625, yet as soon as one casts their value to a string, the 5 "disappears", as it string representation will always just be 3467.6000976562.
xdebug view implying the precision is kept, yet it is lost during the toString conversion
Yet in the second case, both float representation mean exactly the same number, meaning 3467.60009765625318323146 will never be shown as for php, it IS identical to 3467.6000976563.
xdebug view with identical float
See the code in action at: https://onlinephp.io/c/9a245
3 Comments
number_format. I wonder if this behavior is the same in other programming languages or if this is for php only.I believe you want to convert float to string instead of type-casting, in this case you may want to check this answer
Convert float to string in PHP
number_format($we_all_float_down_here, 20, '.', '')
// output
// 3467.60009765625000000000
5may not actually be stored as part of the float's value in the first place. In a nutshell, with floats you can basically never depend on the exact value you'll get for any operation. All that's guaranteed is that you'll get a value close enough to your value, that it'll only occupy a specific memory size, and that operations on it will be pretty fast. For anything else, you need to use a different type. — What exactly is your use case?