我想创建一个函数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位”
  • 如果$value0.0,则log10($value)-INF
  • 由于PHP float的精度为“大约14个十进制数字”,我认为应该显示14个有效数字而不是16个。

  • 我最好的尝试

    这是我想出的最好的解决方案。它基于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/

    10-15 19:45