使用 PHP 截断浮点数

发布于 2024-10-11 18:30:44 字数 827 浏览 9 评论 0 原文

当一个浮点数需要截断为浮点后的某个数字时,事实证明这并不容易做到。例如,如果必须截断到点后第二位数字,则数字应为

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

When a float number needs to be truncated to a certain digit after the floating point, it turns out that it is not easy to be done. For example if the truncating has to be done to second digit after the point, the numbers should be

45.8976 => 45.89, 0.0185 => 0.01

( second digit after the point is not rounded according to the third digit after the point ).

Functions like round(), number_format(), sprintf() round the number and print out

45.8976 => 45.90, 0.0185 => 0.02

I have met two solutions and I am wondering if they are good enough and which one is better to be used

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

咋地 2024-10-18 18:30:45

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

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

其原因是 floor() 总是将数字向下< /strong>,不为零。
floor()有效地将-ve数字朝更大的绝对值舍入
例如 floor(1.5) = 1 while floor(-1.5) = 2

因此,对于使用乘以幂、舍入、然后除以幂的截断方法代码>:
- 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>';
?>

To do this accurately for both +ve and -ve numbers you need use:
- the php floor() function for +ve numbers
- the php ceil() function for -ve numbers

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

the reason for this is that floor() always rounds the number down, not towards zero.
ie floor() effectively rounds -ve numbers towards a larger absolute value
eg floor(1.5) = 1 while floor(-1.5) = 2

Therefore for the truncate method using multiply by power, round, then divide by power:
- floor() only works for positive numbers
- ceil() only works for negative numbers

To test this, copy the following code into the editor of http://phpfiddle.org/lite (or similar):

<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>';
?>
鹿港小镇 2024-10-18 18:30:45

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

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

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

I'm seeing another method to perform this:

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

But it's not better than any other... round can be replaced with sprintf too.

叶落知秋 2024-10-18 18:30:45

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

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

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

The round() function does have a precision paramter as well as a third parameter for specifying the rounding method, but you're right, it doesn't do truncating.

What you're looking for are the floor() and ceil() functions. The downside is that they don't have a precision parameter, so you'll have to do something like this:

$truncated = floor($value*100)/100;
与他有关 2024-10-18 18:30:45

其原因是浮点数的内部二进制表示形式。上面的给定值 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...

要解决此问题,有两种可能性 - 直接使用字符串操作或使用任意精度库,例如 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)

The reason for this is the internal binary representation of floating-point numbers. The given value from above 10.04 has to represented internally as a power of the base 2.

The potential exponent for the floating-point part is ln(0,04)/ln(2) = -4,64.... This already shows that 10.04 cannot be represented exactly as a binary number. We end up having something like 10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ... with having a maximum precision for the floating-point part.

This is the reason that 10.04 is internally actually a bit less and might be represented as 10.039....

To work around this behavior there are two possibilities - either fiddle around directly with string operations or use an arbitrary precision library like BcMath for 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);

The output for the code above will generate (I added separating newlines for readability):

double(3)
double(3)

double(4.6)
double(4.6)

double(10.04)
double(10.04)

double(45.89)
double(45.89)
蝶…霜飞 2024-10-18 18:30:45

我想用我满足我的需求的函数来添加我对这个答案的贡献,该函数考虑了负值和正值的截断位置。

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

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

I would like to add my contribute to this answer with the function I use for my needs, that considers truncate positions both for negative and positive values.

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

...I think it's simple and fast to use.

反目相谮 2024-10-18 18:30:45

你可以试试这个:

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

You can try this:

bcdiv($inputNumber, '1', $precision = 2);
二货你真萌 2024-10-18 18:30:45
function truncNumber($number, $places){
 $strN = preg_split("/\./", (string)$number);
 $num = $strN[0];
 $frac = $strN[1];
 $num .= ($places > 0) ? ".".substr($frac, 0, $places): "";
 return (double)$num;
}
function truncNumber($number, $places){
 $strN = preg_split("/\./", (string)$number);
 $num = $strN[0];
 $frac = $strN[1];
 $num .= ($places > 0) ? ".".substr($frac, 0, $places): "";
 return (double)$num;
}
故事未完 2024-10-18 18:30:45

要解决下限为正数ceil为负数的问题,您只需将数字除以一并使用intdiv()。而浮动问题可以通过使用 round() 乘法之后

所以,这应该有效:

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

Repl.it

输出:

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

To overcome the problem with floor for positives and ceil for negatives, you can just divide the number by one and get the integer part using intdiv(). And the float problem can be solved by using round() after the multiplication.

So, this should work:

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

Repl.it

Outputs:

>>> 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
新雨望断虹 2024-10-18 18:30:45

我用这个:

$number = 0.66666666666;
$number = $number.""
if(strlen($number)>4) {
    $number = substr($number,0, 4);
    $number = floatval($number);
}

输出是0.66

i use this:

$number = 0.66666666666;
$number = $number.""
if(strlen($number)>4) {
    $number = substr($number,0, 4);
    $number = floatval($number);
}

the output is 0.66

Spring初心 2024-10-18 18:30:44

floor 会按照你的要求做。

floor(45.8976 * 100) / 100;

您不会找到更直接的函数来实现此目的,因为这是一种奇怪的问题。通常你会在数学上正确舍入。出于好奇 - 你需要这个做什么?

floor will do as you ask.

floor(45.8976 * 100) / 100;

You won't find a more direct function for this, since it's a kind of odd thing to ask. Normally you'll round mathematically correct. Out of curiosity - What do you need this for?

风吹短裙飘 2024-10-18 18:30:44

这是我的解决方案:

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

this is my solution:

/**
 * @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;
}
零崎曲识 2024-10-18 18:30:44
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 使用 IEEE 754 双精度格式,相对误差约为 1.11e-16。 (resource)

真正的问题是floor函数转换了float值转换为 int 值,例如 (int)(10.04 * 100) = 1003 正如我们之前在 Floor 函数中看到的那样。 (资源)

因此,为了解决这个问题,我们可以将float转换为字符串,字符串可以准确地表示任何值,那么floor函数会将字符串准确地转换为int。

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

Why does PHP not handle mathamatic formulas the way we would expect, e.g. floor(10.04 * 100) = 1003 when we all know it should be 1004.
This issue is not just in PHP but can be found in all langauges, depending on the relative error in the langauge used.
PHP uses IEEE 754 double precision format which has a relative error of about 1.11e-16. (resource)

The real issue is that the floor function casts the float value into an int value, e.g. (int)(10.04 * 100) = 1003 as we see in the floor function earlier. (resource)

So to overcome this issue we can cast the float to a string, a string can represent any value accurately, then the floor function will cast the string to an int accurately.

黎夕旧梦 2024-10-18 18:30:44

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

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

To truncate numbers the "best" is to use (I took a number here that works for my example):

$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

Hope this help is easy than other answers here, but it is easy only for the cases it works for. Here is a case it does not work for (demo):

   $number = 10.046;

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

The number_format function is tricky, you can solve your problem this way (from php.net):

To prevent the rounding that occurs when next digit after last significant decimal is 5 (mentioned by several people below):

<?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
} ?>
任谁 2024-10-18 18:30:44

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

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

我针对具有 1000 万个随机分数的基于字符串的解决方案进行了测试,它们始终匹配。它不会出现基于floor的解决方案所存在的精度问题。

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

Though this question is old, I'll post another answer in case someone stumbles upon this problem like I did. A simple solution for positive numbers is:

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

I tested it against a string-based solution with 10 million random fractions and they always matched. It doesn't exhibit the precision issue that floor-based solutions do.

Extending it for for zero and negatives is quite simple.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文