I would like to create a formatFloat() function that accepts any float and formats it as a decimal extension string. For example:
formatFloat(1.0E+25); // "10,000,000,000,000,000,000,000,000" formatFloat(1.0E+24); // "1,000,000,000,000,000,000,000,000" formatFloat(1.000001); // "1.000001" formatFloat(1.000001E-10); // "0.0000000001000001" formatFloat(1.000001E-11); // "0.00000000001000001"
Initial ideas
Just casting a float for a string will not work, because for a float more than about 1.0E+14 , or less about 1.0E-4 , PHP maps them to scientific notation instead of decimal extension .
number_format() is the obvious PHP function to try. However, this problem occurs for large floats:
number_format(1.0E+25); // "10,000,000,000,000,000,905,969,664" number_format(1.0E+24); // "999,999,999,999,999,983,222,784"
For small floats, the difficulty is choosing the number of decimal digits for the query. One idea is to ask for a large number of decimal digits, and then rtrim() an excess of 0 s. However, this idea is wrong, since the decimal extension often does not end with 0 s:
number_format(1.000001, 30); // "1.000000999999999917733362053696" number_format(1.000001E-10, 30); // "0.000000000100000099999999996746" number_format(1.000001E-11, 30); // "0.000000000010000010000000000321"
The problem is that the floating point number is of limited precision and is usually unable to maintain the exact literal value (for example: 1.0E+25 ). Instead, it retains the highest possible value that can be represented. number_format() expands these "coming approximations".
Timo Frenay Solution
I found this comment deeply immersed in sprintf() , surprisingly without magnification:
Here's how to print a floating point number with 16 significant digits, regardless of size:
$result = sprintf(sprintf('%%.%dF', max(15 - floor(log10($value)), 0)), $value);
The key part is to use log10() to determine the order of magnitude , then to calculate the number of decimal digits.
There are several errors that need to be fixed:
- The code does not work for negative floats.
- The code does not work for extremely small floats (for example:
1.0E-100 ). PHP reports this notification: " sprintf() : The requested precision of 116 digits was truncated to PHP no more than 53 digits" - If
$value is 0.0 , then log10($value) is -INF . - Since the precision of the PHP float is "about 14 decimal digits", I think that instead of 16, 14 significant digits will be displayed.
My best attempt
This is the best solution I've come across. It is based on Timo Frenay's solution, fixes bugs, and uses regex ThiefMaster to trim excess 0 s:
function formatFloat($value) { if ($value == 0.0) return '0.0'; $decimalDigits = max( 13 - floor(log10(abs($value))), 0 ); $formatted = number_format($value, $decimalDigits); // Trim excess 0's $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted); return $formatted; }
Here's a Ideone demo with 200 random floats. The code seems to work correctly for all floats smaller than about 1.0E+15 .
It is interesting to see that number_format() works correctly even for extremely small floats:
formatFloat(1.000001E-250); // "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000001"
Question
My best attempt at formatFloat() still suffers from this problem:
formatFloat(1.0E+25); // "10,000,000,000,000,000,905,969,664" formatFloat(1.0E+24); // "999,999,999,999,999,983,222,784"
Is there an elegant way to improve the code to solve this problem?