skip to Main Content

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?

https://onlinephp.io/c/b3af3


Note:

  • ini_set('precision', 100); won’t fix that certain float values are stored unprecisly
  • number_format may 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. (Similar how in decimal .99999… is identical to 1, even though it looks different, so are the floats 3467.6000976563 and 3467.60009765625318323146 identical for php.)

2

Answers


  1. Chosen as BEST ANSWER

    Don't use floats. Use string when precision is a requirement.


    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


  2. 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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search