微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

PHP:将任何浮点数格式化为十进制扩展

我想创建一个函数formatFloat(),它接受任何浮动并将其格式化为十进制扩展字符串.例如:
formatFloat(1.0E+25);  // "10,000,000"
formatFloat(1.0E+24);  // "1,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的浮点数,PHPscientific notation而不是decimal expansion中呈现它们.

number_format()一个明显的PHP函数.但是,大浮动会出现此问题:

number_format(1.0E+25);  // "10,905,969,664"
number_format(1.0E+24);  // "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的解决方

我在sprintf()页面深处埋没了this comment,令人惊讶的是没有upVotes:

Here is how to print a floating point number with 16 significant digits regardless of magnitude:

$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.
>由于PHP float的精度是“大约14位小数”,我认为应该显示14位有效数字而不是16位数.

我最好的尝试

这是我提出的最佳解决方案.它基于Timo Frenay的解决方案,修复了错误,并使用ThiefMaster’s regex修剪多余的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;
}

Here’s an Ideone demo有200个随机浮点数.代码似乎适用于小于约1.0E 15的所有浮点数.

有趣的是,即使是非常小的浮点数,number_format()也能正常工作:

formatFloat(1.000001E-250);  // "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000001"

这个问题

我对formatFloat()的最佳尝试仍然遇到这个问题:

formatFloat(1.0E+25);  // "10,664"
formatFloat(1.0E+24);  // "999,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;

}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐