4

This is a really simple question. Lets denote the following:

>>> x = 1.2876

Now, round has this great optional second parameter that will round at that decimal place:

>>> round(x,3)
1.288

I was wondering if there is a simple way to round down the numbers. math.floor(x,3) returns an error rather than 1.287

aIKid
  • 26,968
  • 4
  • 39
  • 65
Ryan Saxe
  • 17,123
  • 23
  • 80
  • 128
  • 3
    By "rounding down" do you mean toward 0 or toward negative infinity? It makes a difference for negative numbers (like `floor(-3.1) == -4.0`). – Tim Peters Oct 25 '13 at 02:32
  • By the way, that's just how round behaves, since .876 is closer to .88 than .87 Why would you want it to floor? – aIKid Oct 25 '13 at 02:33
  • Interesting question though. – aIKid Oct 25 '13 at 02:34
  • 1
    Sounds like you want to truncate, answers to this question should help http://stackoverflow.com/questions/783897/truncating-floats-in-python – Nathan Jhaveri Oct 25 '13 at 02:37
  • 1
    What you are asking for doesn't really make sense due to errors in representation of floating point numbers. You could have a result that is close to 1.287 and mathematically your "floor" should be 1.287 (say), but instead you get 1.286 due to errors between what the real result should be vs it's floating point representation – John La Rooy Oct 25 '13 at 02:43
  • @NathanJhaveri I don't think it's related, technically, flooring and truncating is a bit different. – aIKid Oct 25 '13 at 02:51
  • @gnibbler yes I do understand that, but it's not because of floating points all the times. rounding up could get me out of the boundaries where rounding down can never do that – Ryan Saxe Oct 25 '13 at 03:20
  • Why not use `x = min(x, upperboundy)` then? – John La Rooy Oct 25 '13 at 03:24
  • I am using x in mathematical operations. This is for flexible operations so the actual bound is irrelevant at this state, I just cannot risk the number being larger – Ryan Saxe Oct 25 '13 at 03:31

5 Answers5

3

This may be the easiest, if by "rounding down" you mean "toward minus infinity" (as floor() does):

>>> x = 1.2876
>>> x - x % .001
1.287
>>> x = -1.1111
>>> x - x % .001
-1.112

This is prone to lots of shallow surprises, though, due to that most decimal values cannot be exactly represented as binary floating-point values. If those bother you, do something similar with decimal.Decimal values instead.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
2

There's always floor(x*10**3)*10**-3.

Ben
  • 9,184
  • 1
  • 43
  • 56
  • 2
    Note: it's numerically better to divide by 10**3 instead. One rounding error instead of two (10**-3 suffers its own rounding error, and then multiplying by that contributes another rounding error). And it's faster to do floor(x*1e3)/1e3 ;-) – Tim Peters Oct 25 '13 at 02:45
  • 1
    @TimPeters confirmed. if x has five decimal places, `math.floor(x*10**5)*10**-5` results in `1.2876500000000002` – aIKid Oct 25 '13 at 02:47
  • 2
    @aIKid, that's the kind of "shallow surprise" I was referring to in my own answer. It's impossible for _any_ method to avoid those in all cases - binary floats only approximate decimal values. – Tim Peters Oct 25 '13 at 02:52
2

This is just something that appeared in my mind. Why don't we convert it to string, and then floor it?

import math
def floor_float(x, index):
    sx = str(x)
    sx = sx[:index]+str(math.floor(float(sx[index]+"."+sx[index+1])))
    return float(sx)

A little advantage is that it's more representating-error-proof, it's more accurate in representating the numbers (since it's a string):

>>> floor_float(10.8976540981, 8)
10.897654

This maybe not the best pythonic solution though.. But it works quite well :)

Update

In Python 2.x, math.floor returns a float instead of integer. To make this work you'll to convert the result, to an integer:

    sx = sx[:index]+str(int(math.floor(float(sx[index]+"."+sx[index+1]))))

Update2

To be honest, the code above is basically nonsense, and too complicated ;)

Since it's flooring, you can just truncate the string, and float it back:

def floor_float(x, i):
    return float(str(x)[:i])
aIKid
  • 26,968
  • 4
  • 39
  • 65
  • yes this avoids issues with floating points!! I'm not going to use this solution, because I will want the number to represent simply after the decimal, but I think I will go with this technique – Ryan Saxe Oct 25 '13 at 03:22
  • @aIKid, did you edit this code? It doesn't work as shown now; e.g., `floor_float(10.8976540981, 8)` raises `ValueError: invalid literal for float(): 10.897654.0` on the `return` stmt. – Tim Peters Oct 25 '13 at 16:13
  • Are you using python 2.x? I just noticed that it also doesn't work there. Hang on. – aIKid Oct 25 '13 at 22:35
  • @aIKid, Python version doesn't matter - the code that's currently shown is obviously nonsense ;-) – Tim Peters Oct 26 '13 at 02:36
  • :) i know that. Basically you can just truncate the string. That's why i upvoted yours. I didn't upvote mine :) – aIKid Oct 26 '13 at 07:52
2

Another approach, building on the decimal module's more elaborate facilities. Like the builtin round(), this also supports negative "digits":

>>> round(1234.5, -1) # builtin behavior for negative `ndigits`
1230.0
>>> round(1234.5, -2)
1200.0
>>> round(1234.5, -3)
1000.0

and you can use any of the 8(!) rounding modes defined in decimal.

from decimal import ROUND_DOWN
def rfloat(x, ndigits=0, rounding=ROUND_DOWN):
    from decimal import Decimal as D
    proto = D("1e%d" % -ndigits)
    return float(D(str(x)).quantize(proto, rounding))

Example:

for i in range(-4, 6):
    print i, "->", rfloat(-55555.55555, i)

produces:

-4 -> -50000.0
-3 -> -55000.0
-2 -> -55500.0
-1 -> -55550.0
0 -> -55555.0
1 -> -55555.5
2 -> -55555.55
3 -> -55555.555
4 -> -55555.5555
5 -> -55555.55555

Try to parse strings instead at your own risk ;-)

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
1
def roundDown(num, places):
    return int(num*(10**places))/float(10**places)
Brionius
  • 13,858
  • 3
  • 38
  • 49