【发布时间】:2014-03-08 20:09:24
【问题描述】:
我想创建一个函数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"
初步构想
简单地casting 将浮点数转换为字符串将不起作用,因为对于大于大约1.0E+14 或小于大约1.0E-4 的浮点数,PHP 将它们呈现在scientific notation 而不是decimal expansion。
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() 多余的0s。然而,这个想法是有缺陷的,因为十进制扩展通常不会以0s 结尾:
number_format(1.000001, 30); // "1.000000999999999917733362053696"
number_format(1.000001E-10, 30); // "0.000000000100000099999999996746"
number_format(1.000001E-11, 30); // "0.000000000010000010000000000321"
问题是浮点数有limited precision,并且通常无法存储文字的确切值(例如:1.0E+25)。相反,它存储可以表示的最接近的可能值。 number_format() 揭示了这些“最接近的近似值”。
Timo Frenay 的解决方案
我发现this comment 深埋在sprintf() 页面中,令人惊讶的是没有点赞:
以下是如何打印具有 16 位有效数字的浮点数,无论大小如何:
$result = sprintf(sprintf('%%.%dF', max(15 - floor(log10($value)), 0)), $value);
关键部分是使用log10()确定float的order of magnitude,然后计算出所需的小数位数。
有几个错误需要修复:
- 该代码不适用于负浮点数。
- 该代码不适用于极小的浮点数(例如:
1.0E-100)。 PHP 报告此通知:“sprintf(): 请求的 116 位精度被截断为 PHP 最大 53 位” - 如果
$value是0.0,那么log10($value)是-INF。 - 由于 PHP float 的精度是“大约 14 位十进制数字”,我认为应该显示 14 位有效数字而不是 16 位。
我的最佳尝试
这是我想出的最佳解决方案。它基于 Timo Frenay 的解决方案,修复了 bug,并使用 ThiefMaster's regex 修剪多余的 0s:
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 an Ideone demo 带有 200 个随机浮点数。该代码似乎适用于小于大约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"
有没有优雅的方法来改进代码来解决这个问题?
【问题讨论】:
-
您正在寻找的东西类似于 bcmath ;我自己为 php 写了一个 decimal_number 接口; 15年前 ;仍然有代码,但我不知道它是否仍然准确......;通过 log10 只会让你回到你的第一个问题。最好的。
标签: php floating-point floating-point-conversion