删除小数点后两位数字,不进行四舍五入

58

我有一个 PHP 变量中存储了这样的值

$var='2.500000550';
echo $var

我想要的是删除小数点后两位以后的所有数字。

比如现在变量的值为

$var='2.50';
echo $var

请注意这个值是来自MySQL数据库的

但是当我使用round PHP函数时,我得到了一个四舍五入的结果,但我不需要四舍五入,我只需要保留小数点后两位。

我尝试了float()和很多其他选项都没有成功。

谢谢。


你尝试过使用number_format了吗? - hjpotter92
它没有明确的文档说明,但是 number_format 会四舍五入。 - simhumileco
15个回答

146

TL;DR:

使用PHP原生函数bcdiv可以精确地达到所需的目标。

要简单地“截取”一个数字,请使用bcdiv($var, 1, 2);其中2是要保留的小数位数(1是分母 - 将数字除以1可使您简单地将原始数字截断为所需的小数位数)。

Full Answer (for history)

这事实上比人们想象的更加难以实现。

在此答案被(错误地)投了很多票之后,我注意到即使是sprintf也会四舍五入。

为了不删除此答案,我将其转化为每个提议解决方案的更全面的解释/讨论。

number_format - 不正确(进行了四舍五入)
尝试使用number_format

$var = number_format($var, 2, '.', '');  // Last two parameters are optional
echo $var;
// Outputs 2.50
如果你想让它成为一个数字,那么只需将其强制转换为浮点数:
$var = (float)number_format($var, 2, '.', '');

注意:正如在评论中指出的那样,这确实会四舍五入该数字。

sprintf-不正确。(sprintf也会四舍五入)
如果不四舍五入数字很重要,则根据下面的答案使用sprintf

$var = sprintf("%01.2f", $var);

floor - 并不完全符合要求!(floor会将负数四舍五入)

floor 结合一些算法,可以接近实现您想要的功能:

floor(2.56789 * 100) / 100; // 2.56

100代表您想要的精度。如果您希望它为三个数字,则:

floor(2.56789 * 1000) / 1000; // 2.567

然而,这对负数有一个问题。 负数仍然会被四舍五入,而不是被截断:

floor(-2.56789 * 100) / 100; // -2.57

"旧"正确答案:使用 floor 函数的函数

因此,一个完全健壮的解决方案需要一个函数:

function truncate_number( $number, $precision = 2) {
    // Zero causes issues, and no need to truncate
    if ( 0 == (int)$number ) {
        return $number;
    }
    // Are we negative?
    $negative = $number / abs($number);
    // Cast the number to a positive to solve rounding
    $number = abs($number);
    // Calculate precision number for dividing / multiplying
    $precision = pow(10, $precision);
    // Run the math, re-applying the negative value to ensure returns correctly negative / positive
    return floor( $number * $precision ) / $precision * $negative;
}

以上功能的结果:

echo truncate_number(2.56789, 1); // 2.5
echo truncate_number(2.56789);    // 2.56
echo truncate_number(2.56789, 3); // 2.567

echo truncate_number(-2.56789, 1); // -2.5
echo truncate_number(-2.56789);    // -2.56
echo truncate_number(-2.56789, 3); // -2.567

新的正确答案

使用PHP原生函数bcdiv

echo bcdiv(2.56789, 1, 1);  // 2.5
echo bcdiv(2.56789, 1, 2);  // 2.56
echo bcdiv(2.56789, 1, 3);  // 2.567
echo bcdiv(-2.56789, 1, 1); // -2.5
echo bcdiv(-2.56789, 1, 2); // -2.56
echo bcdiv(-2.56789, 1, 3); // -2.567

3
可能需要说明的是,number_format返回的是一个字符串而不是一个数字。 - Philipp Kyeck
1
@MuhammadAtif - 是的,在这篇文章中那不是正确的答案。正确的答案是 bcdiv - random_user_name
1
顺便提一下,如果您只需要删除数字,那么bcadd($num_FLO, 0, $prec_INT)是首选,因为它的工作速度比div或mul操作更快。 - Андрей Аршинов
2
@Bill_VA - 感谢您的评论。实际上,我的代码是正确的(您的也是),而且我的代码遵循一种被称为Yoda Conditions的“防御性”编码模式。如果在_您的_代码中忘记了一个=符号,则会将0无意中赋值给$number变量。如果在_我的_代码中忘记了一个=符号,则会出现错误,PHP将无法运行。 - random_user_name
1
绝对是目前网络上最好的答案 typeof() 表明它也是一个 double! - Sol
显示剩余12条评论

13
floor(2.500000550 * 100) / 100;

这应该能够完成你的任务...


1
这是最简单的方法。number_format仍然会对结果进行四舍五入。 - Justin
在我看来,这应该被标记为答案,@air。所有其他答案都是在截断之前四舍五入的,因此只有当它导致最后一个期望的(在本例中为第二个)小数位增加时才会出现舍入。这种解决方案通过将小数点向右移动两个位置(乘以100),截断(使用floor),然后将其向左移动2个位置(除以100)来避免这种情况。简单而精妙。 - Sepster
2
@Sepster - 实际上,这个存在负数四舍五入的问题。我已经完善了我的答案以解决这种情况。 - random_user_name
1
干得好,@cale_b! - Sepster
这也可以很容易地转换为一个函数,您可以输入要查看的小数位数。 - GrumpyCrouton

6

您正在请求一个返回"2.50"而不是2.5的函数,所以这里不涉及算术运算,而是字符串操作。那么,preg_replace就是您的好帮手:

$truncatedVar = preg_replace('/\.(\d{2}).*/', '.$1', $var);

// "2.500000050" -> "2.50", "2.509" -> "2.50", "-2.509" -> "2.50", "2.5" -> "2.5"

如果你想使用算术方法来完成,只需使用以下代码:
$truncatedVar = round($var * 100) / 100);

// "2.500000050" -> "2.5", "2.599" -> "2.59", "-2.599" -> "2.59"

无法处理二进制系统中周期性的数字,例如尝试运行 'intval(2.092484 * 1000000) / 1000000' => 你会得到 '2.092483'。但是你的 preg_replace 字符串操作非常好,向你致敬,谢谢。 - FantomX1
@FantomX1 你说得完全正确。如果我想要挑剔的话,我会说OP请求两位数字和'intval(2.092484 * 100) / 100'2.09,但我认为使用 round 而不是 intval 应该可以使其更加坚固。谢谢! - flu
好的点数保留两位小数,我个人需要六位。四舍五入会移除无限循环的数字序列,这是正确的,但你并不总是想要四舍五入,比如当我将数据与远程系统进行比较时,MySQL列恰好执行了你的字符串替换,只是从特定的序数位截断。我希望始终返回2.09,无论是2.094还是2.096,因此对我来说四舍五入没有用,但preg_replace可以解决问题。 - FantomX1
那么用 preg_replace('/\.\d{2}\K\d+', '', $var) 怎么样? - mickmackusa

4
使用PHP原生函数 bcdiv示例:
echo bcdiv(3.22871, 1, 1);  // 3.2
echo bcdiv(3.22871, 1, 2);  // 3.22
echo bcdiv(3.22871, 1, 3);  // 3.228
echo bcdiv(-3.22871, 1, 1); // -3.2
echo bcdiv(-3.22871, 1, 2); // -3.22

针对您的情况:
$var='2.500000550';
echo $var
echo bcdiv($var, 1, 2);  // 2.50

4

number_format函数可以对数字进行四舍五入处理

php > echo number_format(128.20512820513, 2)."\n";
128.21

我使用preg_replace来真正截取字符串。
php > echo preg_replace('/(\.\d\d).*/', '$1', 128.20512820513)."\n";
128.20

4

使用number_format进行尝试:

echo number_format('2.50000050', 2); // 2.50

6
这是四舍五入后截取的结果。因此,如果输入的数是2.509,上述过程将返回2.51而不是2.50。因此并没有回答明确声明“不进行四舍五入”的问题。 - Sepster
他还问如何删除,但你仍然保留了分数。 - nights

2

有人在这里发布了一个关于

floor(2.500000550 * 100) / 100;

的帖子。

这段代码是将 2.500000550 向下取整到小数点后两位(即保留两位小数),结果为 2.50。

function cutAfterDot($number, $afterDot = 2){
$a = $number * pow(10, $afterDot);
$b = floor($a);
$c = pow(10, $afterDot);
echo "a $a, b $b, c $c<br/>";
return $b/$c ;
}
echo cutAfterDot(2.05,2);

a 205, b 204, c 100
2.04

所以在原始形式下不要使用它... 但是如果你添加一点 epsilon...

function cutAfterDot($number, $afterDot = 2){
        return floor($number * pow(10, $afterDot) + 0.00001) / pow(10, $afterDot);
    }

它能正常工作!


在这里,floor(205)为什么返回204的解释可以在http://php.net/manual/en/function.floor.php#114204找到。 - Sepster
这实际上会将负数四舍五入。 - random_user_name

1
以下是我认为(如果不正确,请纠正我)一个强大的数学解决方案,主要基于其他回答者的信息和我自己的少量信息。
(*这并不意味着Liphtier的基于正则表达式的答案有什么问题 - 只是我可以看到纯粹主义者想要避免正则表达式,因为这可以说是一个数学问题。)
  • sprintf()number_format()(以及显然的round())都执行舍入操作,因此不适合于问题中所需的非舍入截断(至少不是单独使用)。
  • 如果没有现成的函数,看起来最优雅的解决方案是Sujit Agarwal's answer
  • 但由于浮点数的存储方式,我们需要使用一个小值(epsilon),正如David Constantine's answer中所指出的那样(他还使用了pow()来获取基于指定精度的正确因子,从而使前一个解决方案更加通用)。
  • 但是,正如cale_b's answer中提到的那样,任何使用floor()(或可能的ceil())的方法,在没有使用abs()的情况下可能会产生意想不到的结果。
  • 我要添加的值为:
    • 如果使用abs()上的除法来获取否定因子,则需要考虑输入为0的特殊情况。
    • 我们应该动态创建这个小值;静态硬编码的小值可能太小或太大,具体取决于所需的精度。我没有在其他答案中看到这个问题得到解决。
我正在使用的代码是:


public static function truncate_decimal($number, $leavePlaces = 2)
{
    if ($number == 0) return 0;
    $negate = $number / abs($number);
    $shiftFactor = pow(10, $leavePlaces);
    $epsilon = pow(10, -1 * $leavePlaces);
    return floor(abs($number) * $shiftFactor + $epsilon) / $shiftFactor * $negate;
}

1

使用sprintf

sprintf("%01.2f", $var);

3
这是四舍五入后进行截断。因此,如果$var == 2.509,则上述代码将返回2.51而不是2.50。因此,它无法回答明确声明“不要四舍五入”的问题。 - Sepster

1

已经有很多可能的解决方案了,我只想添加我自己想出来的解决方案来解决我的问题。

function truncate_decimals($number, $decimals=2)
{
  $factor = pow(10,$decimals); 
  $val = intval($number*$factor)/$factor;
  return $val;
}


truncate_decimals(199.9349,2);
// 199.93

刚试了一下,它给出的结果看起来是正确的。 truncate_decimals(10056468765.254564) => 10056468765.25 - victoryoalli
我会进行调查并删除之前的评论。如果我找到了重现步骤,我会在这里发布它们。 - JGCW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接