phpfloating-pointnumber-formattingfloating-point-precision

Format a float with minimum required number of decimal places


I want to format a float with the minimum number of decimal places required to reproduce it.

PHP has a number_format() function for rendering a number with a specified number of decimal places. However, if I use it to format 0.1 with a very high number of decimals, I get:

print rtrim(number_format(0.1, 1000, '.', ''), '0');
// 0.1000000000000000055511151231257827021181583404541015625

Since (float)"0.1" === 0.1, those extra 55511151... decimals after position 16 are useless.

I can use a loop, like this:

function format_float($float) {
    $decimals = 1;
    do {
        $result = number_format($float, $decimals, '.', '');
        $decimals++;
    } while ((float)$result !== $float);
    return $result;
}

print format_float(0.1) . "\n"; // 0.1
print format_float(1/3) . "\n"; // 0.3333333333333333
print format_float(1E-50) . "\n"; // 0.00000000000000000000000000000000000000000000000001

But surely there is a simpler and more efficient way?


Solution

  • This is what I came up with:

    function format_float($num) {
        $dec = $num == 0 ? 0 : ceil(-log10(abs($num)));
        $dec = max(1, $dec + 15 /* magic number */);
        $res = number_format($num, $dec, '.', '');
    
        // sometimes we need one more decimal
        if ((float)$res !== $num) {
            $res = number_format($num, $dec + 1, '.', '');
        }
    
        list($l, $r) = explode('.', $res, 2);
        return "$l." . (rtrim($r) ?: '0');
    }
    

    It assumes the number of decimals needed will be 15 - log10($num) or 16 - log10($num), which seems to hold in practice according to my testing. It's at least more efficient than my brute force loop.