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?
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.