我想创建一个函数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函数。但是,对于大的 float 会发生此问题: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"
问题在于浮点数具有limited precision,并且通常无法存储文字的确切值(例如:
1.0E+25
)。相反,它存储可以表示的最接近的可能值。 number_format()
揭示了这些“最接近的近似值”。Timo Frenay的解决方案
我发现this comment深埋在
sprintf()
页面的深处,令人惊讶地没有投票:$result = sprintf(sprintf('%%.%dF', max(15 - floor(log10($value)), 0)), $value);
关键部分是使用
log10()
确定浮点数的order of magnitude,然后计算所需的小数位数。有一些错误需要修复:
1.0E-100
)。 PHP报告此通知:“sprintf()
:请求的116位精度被截断为PHP最大值53位”$value
是0.0
,则log10($value)
是-INF
。 我最好的尝试
这是我想出的最好的解决方案。它基于Timo Frenay的解决方案,修复了错误,并使用ThiefMaster's regex修剪了多余的
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;
}
带有200个随机浮点数的Here's an Ideone demo。对于所有小于约
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"
是否有一种优雅的方法来改进代码以解决此问题?
最佳答案
这段代码seems to do the job too。我认为我无法使它比您的更优雅,但是我花了很多时间在上面,所以不能丢掉它:)
function formatFloat(
$value,
$noOfDigits = 14,
$separator = ',',
$decimal = '.'
) {
$exponent = floor(log10(abs($value)));
$magnitude = pow(10, $exponent);
// extract the significant digits
$mantissa = (string)abs(round(($value / pow(10, $exponent - $noOfDigits + 1))));
$formattedNum = '';
if ($exponent >= 0) { // <=> if ($value >= 1)
// just for pre-formatting
$formattedNum = number_format($value, $noOfDigits - 1, $decimal, $separator);
// then report digits from $mantissa into $formattedNum
$formattedLen = strlen($formattedNum);
$mantissaLen = strlen($mantissa);
for ($fnPos = 0, $mPos = 0; $fnPos < $formattedLen; $fnPos++, $mPos++) {
// skip non-digit
while($formattedNum[$fnPos] === $separator || $formattedNum[$fnPos] === $decimal || $formattedNum[$fnPos] === '-') {
$fnPos++;
}
$formattedNum[$fnPos] = $mPos < $mantissaLen ? $mantissa[$mPos] : '0';
}
} else { // <=> if ($value < 1)
// prepend minus sign if necessary
if ($value < 0) {
$formattedNum = '-';
}
$formattedNum .= '0' . $decimal . str_repeat('0', abs($exponent) - 1) . $mantissa;
}
// strip trailing decimal zeroes
$formattedNum = preg_replace('/\.?0*$/', '', $formattedNum);
return $formattedNum;
}
关于php - PHP:将任何浮点格式设置为十进制扩展,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/22274437/