可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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 ) );
}
回答1:
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?
回答2:
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;
}
回答3:
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.
回答4:
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
} ?>
回答5:
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.
回答6:
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;
回答7:
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.
回答8:
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>';
?>
回答9:
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)
回答10:
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.
回答11:
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), 1) / $prec;
}
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