web-dev-qa-db-ja.com

浮動小数点数をPHP

浮動小数点の後に浮動小数点数を特定の桁に切り捨てる必要がある場合、それは簡単ではないことがわかります。たとえば、ポイントの後の2桁目まで切り捨てを行う必要がある場合、数値は次のようになります。

_45.8976 => 45.89, 0.0185 => 0.01
_

(ポイントの後の2桁目は、ポイントの後の3桁目に従って丸められません)。

round()number_format()sprintf()のような関数は、数値を丸めて出力します

_45.8976 => 45.90, 0.0185 => 0.02
_

私は2つの解決策に出会いましたが、それらが十分に優れているかどうか、どちらを使用するのが良いか疑問に思っています

1.1。

_function truncNumber( $number, $prec = 2 )
{
    return bccomp( $number, 0, 10 ) == 0 ? $number : round( $number - pow( 0.1, bcadd(   $prec, 1 ) ) * 5, $prec );
}
_

2.2。

_function truncNumber($number, $prec = 2 )
{
    return sprintf( "%.".$prec."f", floor( $number*pow( 10, $prec ) )/pow( 10, $prec ) );
}
_
12

floor あなたが求めるように行います。

floor(45.8976 * 100) / 100;

これは奇妙なことなので、これ以上の直接的な機能は見つかりません。通常、数学的に正しい丸めを行います。好奇心から-これは何のために必要ですか?

21
troelskn

これが私の解決策です:

/**
 * @example truncate(-1.49999, 2); // returns -1.49
 * @example truncate(.49999, 3); // returns 0.499
 * @param float $val
 * @param int f
 * @return float
 */
function truncate($val, $f="0")
{
    if(($p = strpos($val, '.')) !== false) {
        $val = floatval(substr($val, 0, $p + 1 + $f));
    }
    return $val;
}
16
Ragen Dazs
_function truncate($value)
{
    if ($value < 0)
    {
        return ceil((string)($value * 100)) / 100;
    }
    else
    {
        return floor((string)($value * 100)) / 100;
    }
}
_

なぜPHPは、数学式を期待どおりに処理しないのですか。たとえば、floor(10.04 * 100) = 1003は、_1004_である必要があることがわかっているのに。 PHPただし、使用する言語の相対誤差に応じて、すべての言語で検出できます。PHPは、約の相対誤差を持つIEEE754倍精度形式を使用します。 1.11e-16。 (resource)

本当の問題は、floor関数がfloat値をint値にキャストすることです。 _(int)(10.04 * 100) = 1003_前のfloor関数で見たように。 (リソース)

したがって、この問題を克服するために、floatを文字列にキャストできます。文字列は任意の値を正確に表すことができ、floor関数は文字列をintに正確にキャストします。

9
justinrza

数値を切り捨てるには、「最良」を使用することです(ここでは、例として機能する数値を使用しました)。

$number = 120,321314;

$truncate_number  = number_format($number , 1); // 120,3
$truncate_number  = number_format($number , 2); // 120,32
$truncate_number  = number_format($number , 3); // 120,321

このヘルプが他の回答よりも簡単であることを願っていますが、それが機能する場合にのみ簡単です。これは( demo )では機能しない場合です:

   $number = 10.046;

    echo number_format($number , 2); // 10.05

Number_format関数には注意が必要です。この方法で問題を解決できます(php.netから)。

最後の小数点以下の桁が5のときに発生する丸めを防ぐには(以下の数人が言及):

<?php

function fnumber_format($number, $decimals='', $sep1='', $sep2='') {

        if (($number * pow(10 , $decimals + 1) % 10 ) == 5)
            $number -= pow(10 , -($decimals+1));

        return number_format($number, $decimals, $sep1, $sep2);
}

$t=7.15;
echo $t . " | " . number_format($t, 1, '.', ',') .  " | " . fnumber_format($t, 1, '.', ',') . "\n\n";
//result is: 7.15 | 7.2 | 7.1

$t=7.3215;
echo $t . " | " . number_format($t, 3, '.', ',') .  " | " . fnumber_format($t, 3, '.', ',') . "\n\n";
//result is: 7.3215 | 7.322 | 7.321
} ?>
5
sandino

この質問は古いものですが、私のように誰かがこの問題に遭遇した場合に備えて、別の回答を投稿します。正の数の簡単な解決策は次のとおりです。

function truncate($x, $digits) { 
  return round($x - 5 * pow(10, -($digits + 1)), $digits); 
}

私はそれを1000万のランダムな分数を持つ文字列ベースのソリューションに対してテストしましたが、それらは常に一致していました。 floorベースのソリューションのような精度の問題はありません。

ゼロとネガティブのためにそれを拡張することは非常に簡単です。

2
Juan

+ ve-veの両方の数値に対してこれを正確に行うには、次を使用する必要があります。
-+ veの数値に対するphpfloor()関数
-ve番号のphpceil()関数

_function truncate_float($number, $decimals) {
    $power = pow(10, $decimals); 
    if($number > 0){
        return floor($number * $power) / $power; 
    } else {
        return ceil($number * $power) / $power; 
    }
}
_

この理由は、floor()は常に数値を丸めるdownであり、ゼロに向かって。
ie floor()は、-veの数値をより大きな絶対値に効果的に丸めます。
eg floor(1.5) = 1 while floor(-1.5) = 2

したがって、_multiply by power, round, then divide by power_を使用した切り捨てメソッドの場合:
-floor()は正の数に対してのみ機能します
-ceil()は負の数に対してのみ機能します

これをテストするには、次のコードを http://phpfiddle.org/lite (または同様の)のエディターにコピーします。

_<div>Php Truncate Function</div>
<br>
<?php
    function truncate_float($number, $places) {
        $power = pow(10, $places); 
        if($number > 0){
            return floor($number * $power) / $power; 
        } else {
            return ceil($number * $power) / $power; 
        }
    }

    // demo
    $lat = 52.4884;
    $lng = -1.88651;
    $lat_tr = truncate_float($lat, 3);
    $lng_tr = truncate_float($lng, 3);
    echo 'lat = ' . $lat . '<br>';
    echo 'lat truncated = ' . $lat_tr . '<br>';
    echo 'lat = ' . $lng . '<br>';
    echo 'lat truncated = ' . $lng_tr . '<br><br>';

    // demo of floor() on negatives
    echo 'floor (1.5) = ' . floor(1.5) . '<br>';
    echo 'floor (-1.5) = ' . floor(-1.5) . '<br>';
?>
_
2
goredwards

round()関数には、精度パラメーターと、丸め方法を指定するための3番目のパラメーターがありますが、その通り、切り捨ては行われません。

探しているのはfloor()関数とceil()関数です。欠点は、精度パラメータがないことです。そのため、次のような操作を行う必要があります。

$truncated = floor($value*100)/100;
1
Spudley

私はこれを実行する別の方法を見ています:

function trunc($float, $prec = 2) {
    return substr(round($float, $prec+1), 0, -1);
}

しかし、それは他のどの方法よりも優れているわけではありません... roundsprintfに置き換えることもできます。

1
Savageman

負の値と正の値の両方の切り捨て位置を考慮する、ニーズに使用する関数を使用して、この回答への貢献を追加したいと思います。

function truncate($val,$p = 0)
{
  $strpos = strpos($val, '.');
  return $p < 0 ? round($val,$p) : ($strpos ? (float)substr($val,0,$strpos+1+$p) : $val);
}

...簡単で高速に使用できると思います。

0
Nineoclick

この理由は、浮動小数点数の内部バイナリ表現です。上記の_10.04_から指定された値は、基数2の累乗として内部的に表される必要があります。

浮動小数点部分の潜在的な指数はln(0,04)/ln(2) = -4,64...です。これは、_10.04_を2進数として正確に表すことができないことをすでに示しています。 _10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ..._のようなものになり、浮動小数点部分の精度が最大になります。

これが、_10.04_が内部的に実際には少し少なく、_10.039..._として表される可能性がある理由です。

この動作を回避するには、2つの可能性があります。文字列操作を直接操作するか、PHPの場合はBcMathのような任意精度のライブラリを使用します。

_<?php
function test($value)
{
    // direct string operations
    $fromString = (float)preg_replace(
        '#(?:(\.\d{1,2})\d*)?$#',
        '${1}',
        (string)$value
    );
    // using arbitrary precision
    // @see http://php.net/manual/en/function.bcadd.php
    $fromBcMath = (float)bcadd(
        (string)$value,
        '0',
        2
    );

    var_dump($fromString, $fromBcMath);
}

test(3);
test(4.60);
test(10.04);
test(45.8976);
_

上記のコードの出力は次のように生成されます(読みやすくするために改行を区切ることを追加しました)。

_double(3)
double(3)

double(4.6)
double(4.6)

double(10.04)
double(10.04)

double(45.89)
double(45.89)
_
0
Oliver Hader

正の場合は、負の場合は天井の問題を克服するには、数値を1で割って、整数部分を取得します。 intdiv() を使用します。そして、floatの問題は、乗算の後に round()afterを使用することで解決できます。

したがって、これは機能するはずです。

<?php
function truncate($number, $precision = 2) {
    $prec = 10 ** $precision;
    return intdiv(round($number * $prec), 1) / $prec;
}

出力:

>>> truncate(45.8976)
=> 45.89
>>> truncate(-45.8976)
=> -45.89
>>> truncate(3)
=> 3
>>> truncate(-3)
=> -3
>>> truncate(10.04)
=> 10.04
>>> truncate(-10.04)
=> -10.04
0
fernandosavio