我希望创建一个名为
对于小数,难点在于选择要求多少位小数。一种想法是要求大量的小数位数,然后使用
问题在于浮点数具有有限的精度,通常无法存储文字值的确切值(例如:
关键部分是使用
有几个需要修复的错误:
有没有一种优雅的方式来改进代码以解决这个问题?
formatFloat()
的函数,它可以将任何浮点数格式化为十进制扩展字符串。例如: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"
最初的想法
仅将浮点数转换为字符串是不行的,因为对于大于约 1.0E+14
或小于约 1.0E-4
的浮点数,PHP 会用科学计数法来渲染它们,而非十进制展开。
number_format()
是尝试解决此问题的明显 PHP 函数。但是,对于大浮点数仍然存在此问题:
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"
对于小数,难点在于选择要求多少位小数。一种想法是要求大量的小数位数,然后使用
rtrim()
去除多余的0
。然而,这个想法是有缺陷的,因为小数扩展通常不以0
结尾。number_format(1.000001, 30); // "1.000000999999999917733362053696"
number_format(1.000001E-10, 30); // "0.000000000100000099999999996746"
number_format(1.000001E-11, 30); // "0.000000000010000010000000000321"
问题在于浮点数具有有限的精度,通常无法存储文字值的确切值(例如:
1.0E+25
)。相反,它存储最接近可表示的可能值。 number_format()
显示这些“最接近的近似值”。
Timo Frenay的解决方案
我发现这个评论深埋在sprintf()
页面中,令人惊讶的是没有赞:
以下是如何打印具有16位有效数字的浮点数,无论大小:
$result = sprintf(sprintf('%%.%dF', max(15 - floor(log10($value)), 0)), $value);
关键部分是使用
log10()
来确定浮点数的数量级,从而计算所需的小数位数。有几个需要修复的错误:
- 该代码无法处理负浮点数。
- 该代码无法处理极小的浮点数(例如:
1.0E-100
)。PHP会报告以下通知:“sprintf()
: 请求的精度为116位数字,已被截断为PHP最大的53位数字” - 如果
$value
为0.0
,那么log10($value)
将为-INF
。 - 由于PHP float的精度大约为“14位小数”,因此应显示14个有效数字,而不是16个。
我的最佳尝试
这是我想出的最佳解决方案。它基于Timo Frenay的解决方案,修复了错误,并使用ThiefMaster的正则表达式来修剪多余的0
:
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;
}
这里有一个包含200个随机浮点数的Ideone演示。该代码似乎对所有小于约1.0E+15
的浮点数都能正确运行。
有趣的是,即使对极小的浮点数,number_format()
也能正确工作:
formatFloat(1.000001E-250); // "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000001"
问题
我最好的 formatFloat()
尝试仍然存在这个问题:
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"
有没有一种优雅的方式来改进代码以解决这个问题?