用PHP截断浮点数

时间:2011-01-12 12:11:31

标签: php floating-point truncate floating-accuracy

当浮点数需要在浮点数之后被截断为某个数字时,事实证明它不容易完成。例如,如果必须在点之后对第二个数字进行截断,则数字应为
45.8976 => 45.89,0.0185 => 0.01
(点之后的第二个数字不会根据点后的第三个数字进行舍入) round(),number_format(),sprintf()等函数围绕数字并打印出来 45.8976 => 45.90,0.0185 => 0.02

我遇到了两个解决方案,我想知道它们是否足够好以及哪一个更适合使用

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

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

12 个答案:

答案 0 :(得分:20)

floor会按照您的要求执行。

floor(45.8976 * 100) / 100;

你不会找到更直接的功能,因为这是一个奇怪的问题。通常你会在数学上正确地纠正。出于好奇 - 你需要做什么?

答案 1 :(得分:16)

这是我的解决方案:

/**
 * @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;
}

答案 2 :(得分:9)

function truncate($value)
{
    if ($value < 0)
    {
        return ceil((string)($value * 100)) / 100;
    }
    else
    {
        return floor((string)($value * 100)) / 100;
    }
}

为什么PHP不像我们期望的那样处理mathamatic公式,例如floor(10.04 * 100) = 1003当我们都知道它应该是1004时。 这个问题不仅存在于PHP中,而且可以在所有语言中找到,具体取决于所使用的语言中的相对错误。 PHP使用IEEE 754双精度格式,其相对误差约为1.11e-16。 (resource)

真正的问题是floor函数将float值转换为int值,例如:正如我们之前在楼层功能中看到的(int)(10.04 * 100) = 1003(resource)

因此,为了克服这个问题,我们可以将float转换为字符串,字符串可以准确表示任何值,然后floor函数将字符串精确地转换为int。

答案 3 :(得分:6)

要截断数字,使用“最佳”(我在这里使用了一个适用于我的例子的数字):

$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
} ?>

答案 4 :(得分:2)

虽然这个问题已经过时了,但我会发布另一个答案,以防有人像我一样偶然发现这个问题。一个简单的正数解决方案是:

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

我针对基于字符串的解决方案对其进行了测试,该解决方案包含1000万个随机分数,并且它它没有表现出基于floor的解决方案所做的精度问题。

将其扩展为零和负数非常简单。

答案 5 :(得分:1)

round()函数确实有一个精度参数以及用于指定舍入方法的第三个参数,但是你是对的,它不会截断。

您正在寻找的是floor()ceil()功能。缺点是它们没有精度参数,所以你必须做这样的事情:

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

答案 6 :(得分:1)

我正在看另一种方法来执行此操作:

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

但它并不比其他任何人更好...... round也可以替换为sprintf

答案 7 :(得分:1)

要准确地为+ ve -ve数字执行此操作,您需要使用:
- + ve数字的php floor()函数
- 用于-ve numbers的php ceil()函数

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

原因是floor()总是将数字 向下 ,而不是零。 即floor()有效地舍入-ve数朝向更大的绝对值
例如floor(1.5) = 1floor(-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>';
?>

答案 8 :(得分:0)

我想用我用于我需要的函数添加我对这个答案的贡献,它考虑截断位置的负值和正值。

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

......我认为使用它简单快捷。

答案 9 :(得分:0)

原因是浮点数的内部二进制表示。上面10.04的给定值必须在内部表示为基数2的幂。

浮点部分的潜在指数为ln(0,04)/ln(2) = -4,64...。这已经表明10.04不能完全表示为二进制数。我们最终得到像10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ...这样的东西,它具有浮点部分的最大精度。

这就是10.04内部实际上少一点的原因,可能表示为10.039...

要解决此问题,有两种可能性 - 要么直接使用字符串操作,要么使用像BcMath这样的任意精度库用于PHP。

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

答案 10 :(得分:0)

要克服底数为正数底数为负数的问题,您可以将数字除以1,然后使用intdiv()获得整数部分。浮点问题可以通过在乘法之后的{strong> 之后使用round()来解决。

所以,这应该起作用:

<?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

答案 11 :(得分:-1)

您可以尝试以下方法:

bcdiv($inputNumber, '1', $precision = 2);