当浮点数需要在浮点数之后被截断为某个数字时,事实证明它不容易完成。例如,如果必须在点之后对第二个数字进行截断,则数字应为
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 ) );
}
答案 0 :(得分:20)
答案 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) = 1
而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>';
?>
答案 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);