使用 PHP 截断浮点数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4668628/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Truncate float numbers with PHP
提问by Dessislava Mitova
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
函数如round()
, number_format()
,sprintf()
四舍五入并打印出来
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.
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 ) );
}
回答by troelskn
回答by Ragen Dazs
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;
}
回答by justinrza
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)
为什么 PHP 不以我们期望的方式处理数学公式,例如,floor(10.04 * 100) = 1003
当我们都知道它应该是1004
. 这个问题不仅在 PHP 中存在,而且可以在所有语言中找到,具体取决于所用语言的相对误差。PHP 使用 IEEE 754 双精度格式,其相对误差约为 1.11e-16。(资源)
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)
真正的问题是 floor 函数将 float 值转换为 int 值,例如(int)(10.04 * 100) = 1003
我们之前在 floor 函数中看到的。(资源)
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.
所以为了克服这个问题,我们可以将float转换为字符串,一个字符串可以准确地表示任何值,然后floor函数会将字符串准确地转换为int。
回答by sandino
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):
number_format 函数很棘手,您可以通过这种方式解决您的问题(来自 php.net):
To prevent the rounding that occurs when next digit after last significant decimal is 5 (mentioned by several people below):
为了防止在最后一位有效小数点后的下一位数字为 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
} ?>
回答by Juan
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.
我针对具有 1000 万个随机分数的基于字符串的解决方案对其进行了测试,并且它们始终匹配。它没有表现出floor
基于解决方案的精度问题。
Extending it for for zero and negatives is quite simple.
将其扩展为零和负数非常简单。
回答by goredwards
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
要对 +ve和-ve 数字准确地执行此操作,您需要使用:
- floor()
+ve 数字
的 phpceil()
函数- -ve 数字的 php函数
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
这样做的原因是floor()
总是将数字向下舍入,而不是向零舍入。
即floor()
有效地将 -ve 数字向更大的绝对值四舍五入,
例如floor(1.5) = 1
whilefloor(-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
因此,对于使用的截断方法multiply by power, round, then divide by power
:
-floor()
仅适用于正数
-ceil()
仅适用于负数
To test this, copy the following code into the editor of http://phpfiddle.org/lite(or similar):
要对此进行测试,请将以下代码复制到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>';
?>
回答by Spudley
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.
该round()
函数确实有一个精度参数以及用于指定舍入方法的第三个参数,但您是对的,它不进行截断。
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:
您正在寻找的是floor()
和ceil()
功能。缺点是它们没有精度参数,因此您必须执行以下操作:
$truncated = floor($value*100)/100;
回答by Savageman
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.
但它并不比任何其他更好......round
也可以替换sprintf
。
回答by Nineoclick
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.
...我认为它使用起来既简单又快速。
回答by Oliver Hader
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.
原因是浮点数的内部二进制表示。上面的给定值10.04
必须在内部表示为底数 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.
浮点部分的潜在指数是ln(0,04)/ln(2) = -4,64...
。这已经表明10.04
不能完全表示为二进制数。我们最终10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ...
获得了浮点部分的最大精度。
This is the reason that 10.04
is internally actually a bit less and might be represented as 10.039...
.
这就是10.04
内部实际上少一些并且可能表示为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.
要解决此行为,有两种可能性 - 直接使用字符串操作或使用任意精度的库(如BcMath
PHP)。
<?php
function test($value)
{
// direct string operations
$fromString = (float)preg_replace(
'#(?:(\.\d{1,2})\d*)?$#',
'',
(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)