57

I need to round down a decimal in PHP to two decimal places so that:

49.955

becomes...

49.95

I have tried number_format, but this just rounds the value to 49.96. I cannot use substr because the number may be smaller (such as 7.950). I've been unable to find an answer to this so far.

Any help much appreciated.

Adam Moss
  • 5,582
  • 13
  • 46
  • 64

16 Answers16

108

This can work: floor($number * 100) / 100

GeoffreyB
  • 1,791
  • 4
  • 20
  • 36
25

Unfortunately, none of the previous answers (including the accepted one) works for all possible inputs.

1) sprintf('%1.'.$precision.'f', $val)

Fails with a precision of 2 : 14.239 should return 14.23 (but in this case returns 14.24).

2) floatval(substr($val, 0, strpos($val, '.') + $precision + 1))

Fails with a precision of 0 : 14 should return 14 (but in this case returns 1)

3) substr($val, 0, strrpos($val, '.', 0) + (1 + $precision))

Fails with a precision of 0 : -1 should return -1 (but in this case returns '-')

4) floor($val * pow(10, $precision)) / pow(10, $precision)

Although I used this one extensively, I recently discovered a flaw in it ; it fails for some values too. With a precision of 2 : 2.05 should return 2.05 (but in this case returns 2.04 !!)

So far the only way to pass all my tests is unfortunately to use string manipulation. My solution based on rationalboss one, is :

function floorDec($val, $precision = 2) {
    if ($precision < 0) { $precision = 0; }
    $numPointPosition = intval(strpos($val, '.'));
    if ($numPointPosition === 0) { //$val is an integer
        return $val;
    }
    return floatval(substr($val, 0, $numPointPosition + $precision + 1));
}

This function works with positive and negative numbers, as well as any precision needed.

yivi
  • 42,438
  • 18
  • 116
  • 138
Alex
  • 1,241
  • 13
  • 22
  • 1
    Hey, I realize this was a long time ago, but even your solution breaks if the `$val` is a float in scientific notation like `-3E-15` – zedling Feb 25 '19 at 13:09
  • 1
    @zedling indeed, `floorDev` expect a 'classic' notation. I guess you could check for 'e' of 'E' in `$val`, then convert to the string notation. – Alex Feb 27 '19 at 06:15
24

Here is a nice function that does the trick without using string functions:

<?php
function floorp($val, $precision)
{
    $mult = pow(10, $precision); // Can be cached in lookup table        
    return floor($val * $mult) / $mult;
}

print floorp(49.955, 2);
?>

An other option is to subtract a fraction before rounding:

function floorp($val, $precision)
{
    $half = 0.5 / pow(10, $precision); // Can be cached in a lookup table
    return round($val - $half, $precision);
}
huysentruitw
  • 27,376
  • 9
  • 90
  • 133
  • 1
    Using subtract actually works even with numbers like floorp(2.05, 2) (first function doesn't). To handle negative numbers properly, third round() parameter must be used: round($val - $half, $precision, $val <= 0 ? PHP_ROUND_HALF_DOWN : PHP_ROUND_HALF_UP) – sidon Aug 08 '19 at 10:09
21

I think there is quite a simple way to achieve this:

$rounded = bcdiv($val, 1, $precision);

Here is a working example. You need BCMath installed but I think it's normally bundled with a PHP installation. :) Here is the documentation.

Jamie Robinson
  • 832
  • 10
  • 16
8
function roundDown($decimal, $precision)
{
    $sign = $decimal > 0 ? 1 : -1;
    $base = pow(10, $precision);
    return floor(abs($decimal) * $base) / $base * $sign;
}

// Examples
roundDown(49.955, 2);           // output: 49.95
roundDown(-3.14159, 4);         // output: -3.1415
roundDown(1000.000000019, 8);   // output: 1000.00000001

This function works with positive and negative decimals at any precision.

Code example here: http://codepad.org/1jzXjE5L

Sterling Beason
  • 622
  • 6
  • 12
7

Multiply your input by 100, floor() it, then divide the result by 100.

GordonM
  • 31,179
  • 15
  • 87
  • 129
5

You can use bcdiv PHP function.

bcdiv(49.955, 1, 2)
Sanaullah Ahmad
  • 474
  • 4
  • 12
4

Try the round() function

Like this: round($num, 2, PHP_ROUND_HALF_DOWN);

huysentruitw
  • 27,376
  • 9
  • 90
  • 133
user1606963
  • 105
  • 1
  • 5
  • 1
    The OP said he doesn't want to ever round up. Round() rounds up at .5 or greater. – GordonM Sep 05 '12 at 09:14
  • round($num, 2, PHP_ROUND_HALF_DOWN);? – user1606963 Sep 05 '12 at 09:18
  • Yeah, I'd forgotten about that. But you didn't mention it in your original answer :) (I'm not the one who downvoted you, BTW) – GordonM Sep 05 '12 at 09:22
  • 12
    @user1606963 PHP_ROUND_HALF_DOWN only determines what happens for the halfway decimal exactly...it does not mimic floor which rounds down at the halfway OR GREATER. For example `round(1.115, 2, PHP_ROUND_HALF_DOWN);` will round down to 1.11, but `round(1.116, 2, PHP_ROUND_HALF_DOWN);` will still round up to 1.12, which is not the desired outcome. The answers about multiplying by 100, flooring, and dividing by 100 are the only ways that I know of that accomplish rounding to 2 decimals while always rounding down. (barring converting to a string and manipulating that way of course) – Jimbo Jonny Jun 11 '13 at 19:53
3

For anyone in need, I've used a little trick to overcome math functions malfunctioning, like for example floor or intval(9.7*100)=969 weird.

function floor_at_decimals($amount, $precision = 2)
{
    $precise = pow(10, $precision);
    return floor(($amount * $precise) + 0.1) / $precise;
}

So adding little amount (that will be floored anyways) fixes the issue somehow.

George G
  • 7,443
  • 12
  • 45
  • 59
1

Use formatted output

sprintf("%1.2f",49.955) //49.95

DEMO

Zoltan Toth
  • 46,981
  • 12
  • 120
  • 134
GentSVK
  • 324
  • 1
  • 3
  • 12
1

You can use:

$num = 49.9555;
echo substr($num, 0, strpos($num, '.') + 3);
huysentruitw
  • 27,376
  • 9
  • 90
  • 133
rationalboss
  • 5,330
  • 3
  • 30
  • 50
1
function floorToPrecision($val, $precision = 2) {
        return floor(round($val * pow(10, $precision), $precision)) / pow(10, $precision);
    }
  • Please add some explanation to your code - there are already multiple other answers, and you should explain what makes yours better than the others, or even different – Nico Haase Mar 11 '19 at 11:38
0

An alternative solution using regex which should work for all positive or negative numbers, whole or with decimals:

if (preg_match('/^-?(\d+\.?\d{1,2})\d*$/', $originalValue, $matches)){
    $roundedValue = $matches[1];
} else {
    throw new \Exception('Cannot round down properly '.$originalValue.' to two decimal places');
}
Luke Cousins
  • 2,068
  • 1
  • 20
  • 38
0

Based on @huysentruitw and @Alex answer, I came up with following function that should do the trick.

It pass all tests given in Alex's answer (as why this is not possible) and build upon huysentruitw's answer.

function trim_number($number, $decimalPlaces) {
    $delta = (0 <=> $number) * (0.5 / pow(10, $decimalPlaces));
    $result = round($number + $delta, $decimalPlaces);
    return $result ?: 0; // get rid of negative zero
}

The key is to add or subtract delta based on original number sign, to support trimming also negative numbers. Last thing is to get rid of negative zeros (-0) as that can be unwanted behaviour.

Link to "test" playground.

EDIT: bcdiv seems to be the way to go.

// round afterwards to cast 0.00 to 0
// set $divider to 1 when no division is required
round(bcdiv($number, $divider, $decimalPlaces), $decimalPlaces);
sidon
  • 1,434
  • 1
  • 17
  • 30
-2
sprintf("%1.2f",49.955) //49.95

if you need to truncate decimals without rounding - this is not suitable, because it will work correctly until 49.955 at the end, if number is more eg 49.957 it will round to 49.96
It seems for me that Lght`s answer with floor is most universal.

ishubin
  • 303
  • 3
  • 2
-5

Did you try round($val,2) ?

More information about the round() function

Zoltan Toth
  • 46,981
  • 12
  • 120
  • 134
Raphaël Michel
  • 179
  • 1
  • 4