8

formatFloat()float を取り、それを 10 進展開文字列としてフォーマットする関数を作成したいと思います。例えば:

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"

初期のアイデア

float を文字列にキャストするだけではうまくいきません。なぜなら、 float が about よりも大きいか、 about1.0E+14よりも小さい場合1.0E-4、PHP はそれらを10 進数展開ではなく指数表記でレンダリングするからです。

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"

小さい float の場合、難しいのは、要求する 10 進数の桁数を選択することです。1 つのアイデアは、多数の 10 進数を要求してからrtrim()、余分な0s を要求することです。0ただし、10 進展開がsで終わらないことが多いため、この考えには欠陥があります。

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);

重要な部分は、 を使用して float のlog10()数を決定し、次に必要な 10 進数の桁数を計算することです。

修正が必要なバグがいくつかあります。

  • このコードは、負の浮動小数点では機能しません。
  • このコードは、非常に小さな float (例: ) では機能しません1.0E-100。PHP は次の通知を報告します: " sprintf(): 要求された 116 桁の精度は、PHP の最大 53 桁に切り捨てられました"
  • である場合$valueはです。0.0log10($value)-INF
  • PHP floatの精度は「おおよそ 10 進数で 14 桁」なので、16 桁ではなく 14 桁を表示する必要があると思います。

私の最善の試み

これが私が思いついた最善の解決策です。これは Timo Frenay のソリューションに基づいており、バグを修正し、余分なsをトリミングするために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"

この問題を解決するためにコードを改善するエレガントな方法はありますか?

4

3 に答える 3

1

私はこの(かなり洗練されていない)ソリューションを作成することができました。

フロートが より小さい場合1.0E+14、私の質問の「最善の試み」コードが使用されます。それ以外の場合は、整数部分を有効数字 14 桁に丸めます。

これは 500 個のランダムなフロートを使用した Ideone のデモで、コードはそれらすべてに対して正しく動作しているようです。

私が言うように、これはあまりエレガントな実装ではないので、誰かがより良い解決策を考案できるかどうかを知りたいと思っています.

function formatFloat($value)
{
    $phpPrecision = 14;

    if ($value == 0.0)  return '0.0';

    if (log10(abs($value)) < $phpPrecision) {

        $decimalDigits = max(
            ($phpPrecision - 1) - floor(log10(abs($value))),
            0
        );

        $formatted = number_format($value, $decimalDigits);

        // Trim excess 0's
        $formatted = preg_replace('/(\.[0-9]+?)0*$/', '$1', $formatted);

        return $formatted;

    }

    $formattedWithoutCommas = number_format($value, 0, '.', '');

    $sign = (strpos($formattedWithoutCommas, '-') === 0) ? '-' : '';

    // Extract the unsigned integer part of the number
    preg_match('/^-?(\d+)(\.\d+)?$/', $formattedWithoutCommas, $components);
    $integerPart = $components[1];

    // Split into significant and insignificant digits
    $significantDigits   = substr($integerPart, 0, $phpPrecision);
    $insignificantDigits = substr($integerPart, $phpPrecision);

    // Round the significant digits (using the insignificant digits)
    $fractionForRounding = (float) ('0.' . $insignificantDigits);
    $rounding            = (int) round($fractionForRounding);  // Either 0 or 1
    $rounded             = $significantDigits + $rounding;

    // Pad on the right with zeros
    $formattingString = '%0-' . strlen($integerPart) . 's';
    $formatted        = sprintf($formattingString, $rounded);

    // Insert a comma between every group of thousands
    $formattedWithCommas = strrev(
        rtrim(
            chunk_split(
                strrev($formatted), 3, ','
            ),
            ','
        )
    );

    return $sign . $formattedWithCommas;
}
于 2014-03-13T20:42:08.660 に答える
-2
number_format($result, 14, '.', '');
于 2014-11-11T17:56:37.557 に答える