22

I need to floor a float number with an specific number of decimals.

So:

2.1235 with 2 decimals --> 2.12
2.1276 with 2 decimals --> 2.12  (round would give 2.13 which is not what I need)

The function np.round accepts a decimals parameter but it appears that the functions ceil and floor don't accept a number of decimals and always return a number with zero decimals.

Of course I can multiply the number by 10^ndecimals, then apply floor and finally divide by 10^ndecimals

new_value = np.floor(old_value * 10**ndecimals) / 10**ndecimals

But I'm wondering if there's a built-in function that does this without having to do the operations.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
  • Possible duplicate of [https://stackoverflow.com/questions/455612/limiting-floats-to-two-decimal-points](https://stackoverflow.com/questions/455612/limiting-floats-to-two-decimal-points) – techytushar Sep 23 '19 at 14:56
  • This question asks for a number to be rounded. I don't want to round –  Sep 23 '19 at 14:57

4 Answers4

22

Neither Python built-in nor numpy's version of ceil/floor support precision.

One hint though is to reuse round instead of multiplication + division (should be much faster):

def my_ceil(a, precision=0):
    return np.round(a + 0.5 * 10**(-precision), precision)

def my_floor(a, precision=0):
    return np.round(a - 0.5 * 10**(-precision), precision)

UPD: As pointed out by @aschipfl, for whole values np.round will round to the nearest even, which will lead to unexpected results, e.g. my_ceil(11) will return 12. Here is an updated solution, free of this problem:

def my_ceil(a, precision=0):
    return np.true_divide(np.ceil(a * 10**precision), 10**precision)

def my_floor(a, precision=0):
    return np.true_divide(np.floor(a * 10**precision), 10**precision)
Asclepius
  • 57,944
  • 17
  • 167
  • 143
Marat
  • 15,215
  • 2
  • 39
  • 48
  • 1
    I like this solution. Any idea why `ceil` and `floor` don't have a decimals parameter? It would be useful if the implementation allows it. –  Sep 23 '19 at 15:16
  • 2
    I don't know. Maybe it is because other languages didn't have it. I agree it would be helpful to have – Marat Sep 23 '19 at 15:33
  • Just a heads up, because of how python keeps floating point numbers manageable by displaying a rounded value instead the true machine value (See [https://docs.python.org/2/tutorial/floatingpoint.html] this solution appears to work if you print the result. But when I used pythons built in decimal module to display the exact value that’s stored in in the float I noticed that it is still not a perfect floor or ceil. This is fine if you are calculating or printing the result, but could have an effect if you want to apply further operations after calculating the floor of the ceiling. – B. Ackerman Sep 23 '19 at 16:21
  • `$ Decimal(np.round(1.128345345 + 0.5 * 10**(-2), 2)) Decimal('1.12999999999999989341858963598497211933135986328125')` – B. Ackerman Sep 23 '19 at 16:24
  • Note that this output comes directly from `round`. Numpy explicitly mentions in the documentation its `round` is less precise than native Python version – Marat Sep 23 '19 at 16:57
  • @B.Ackerman The reason for that is 10 not being a power of 2 that most decimal fractions have no exact representation as floats. Try `decimal.Decimal(0.1)`. As long as you are using floats there is nothing that can be done about this. – Paul Panzer Sep 24 '19 at 01:41
  • Would this not return wrong results when `a` is an integer, because `np.round()` rounds to the nearest even integer? – aschipfl Feb 28 '21 at 21:47
  • @aschipfl you're right, and it also affects floats. I do not have an immediate solution that will work equally well for all types of input, but will add later when/if I find something – Marat Mar 01 '21 at 04:51
  • If I am not mistaken, the integer problem could be resolved by using the built-in `round` function, which rounds up when the argument is exactly halfway between two integers, but signs must then be dealt with specifically… – aschipfl Mar 01 '21 at 09:53
5

This seems to work (needs no import and works using the // operator which should be faster than numpy, as it simply returns the floor of the division):

a = 2.338888
n_decimals = 2
a = ((a*10**n_decimals)//1)/(10**n_decimals)
tsb5555
  • 321
  • 3
  • 13
  • Yes, this is almost the same as I'm doing. I prefer using `floor` since sometimes I'm doing `ceil` instead and I can have the code with the same syntax for readability –  Sep 23 '19 at 15:15
  • @SembeiNorimaki This runs in slightly less than half the time as compared to the numpy method that you mentioned in the question and is 13-14 times faster than the accepted answer (which is slowest of the 3 methods). Creating a custom function with this method is the best approach. – tsb5555 Sep 23 '19 at 16:35
0

If regular expressions are an option, you can give this a go:

import re

def truncate(n, d):
  return float(re.search('\d+\.\d{}'.format(d), str(float(n)))[0])

print(truncate(2.1235, 2))
print(truncate(2.1276, 2))

Output:

2.12
2.12

Another solution using str.split:

def truncate(n, d):
  s = str(float(n)).split('.')
  return float('{}.{}'.format(s[0], s[1][:d]))

print(truncate(2.1235, 2))
print(truncate(2.1276, 2))

Output:

2.12
2.12
DjaouadNM
  • 22,013
  • 4
  • 33
  • 55
  • 1
    This works, but I think this is even more complicated than just doing the multiplication and division that I'm currently doing –  Sep 23 '19 at 14:59
  • 1
    Passing an `int` to this could lead to surprises (if it's not always guaranteed you're going to get floats...) – Jon Clements Sep 23 '19 at 15:21
  • @JonClements You're right, casting `n` to `float` solves this. – DjaouadNM Sep 23 '19 at 15:23
-2

For C++ devs or a general solution that worked for me:

float a, b;

// a = 2.05
// b = 2

c = (ceil((a-b)*1000))/1000;
The Flash
  • 59
  • 7