17

I need to round down and it should be two decimal places.
Tried the following,

a = 28.266
print round(a, 2)

28.27

But the expected value is 28.26 only.

Suresh Kumar
  • 415
  • 2
  • 5
  • 14
  • 1
    You want to truncate. Have a look here: http://stackoverflow.com/questions/8595973/truncate-to-3-decimals-in-python – zinjaai Dec 29 '16 at 16:45
  • 1
    @zinjaai is right half of the time if you reference the top answer. However, consider the comment `'%.3f'%(1324343032.3243) and '%.3f'%(1324343032.3245) give different results` This is because the top answer is actually equivalent to rounding. I would just use @Psidom's answer here instead. – Corey Levinson Jun 11 '19 at 19:20

6 Answers6

26

Seems like you need the floor:

import math
math.floor(a * 100)/100.0

# 28.26
Psidom
  • 171,477
  • 20
  • 249
  • 286
17

It seems you want truncation, not rounding.

A simple way would be to combine floor division // and regular division /:

>>> a = 28.266
>>> a // 0.01 / 100
28.26

Instead of the regular division you could also multiply (as noted in the comments by cmc):

>>> a // 0.01 * 0.01
28.26

Similarly you could create a function to round down to other more/less decimals. But because floats are inexact numbers, this can lead to inaccuracies.

def round_down(value, decimals):
    factor = 1 / (10 ** decimals)
    return (value // factor) * factor

print(round_down(28.266, 2))
# 28.26

But as said it's not exactly exact:

for i in range(0, 8):
    print(i, round_down(12.33333, i))
0 12.0
1 12.3
2 12.33
3 12.333
4 12.333300000000001 # weird, but almost correct
5 12.33332           # wrong
6 12.33333
7 12.33333

There are other (more precise) approaches though:

A solution using the fraction module

A fraction can represent a decimal number much more exact than a float. Then one can use the "multiply, then floor, then divide" approach mentioned by Psidom but with significantly higher precision:

import fractions
import math

a = 28.266

def round_down(value, decimals):
    factor = 10 ** decimals
    f = fractions.Fraction(value)
    return fractions.Fraction(math.floor(f * factor),  factor)

print(round_down(28.266, 2))
# 1413/50  <- that's 28.26

And using the test I did with the floats:

for i in range(0, 8):
    print(i, round_down(12.33333, i))
0 12
1 123/10
2 1233/100
3 12333/1000
4 123333/10000
5 1233333/100000
6 1233333/100000
7 1233333/100000

However creating a Fraction will not magically fix an inexact float, so typically one should create the Fraction from a string or a "numerator-denominator pair" instead of from float.

A solution using the decimal module

You could also use the decimal module, which offers a variety of rounding modes, including rounding down.

For this demonstration I'm using a context manager to avoid changing the decimal rounding mode globally:

import decimal

def round_down(value, decimals):
    with decimal.localcontext() as ctx:
        d = decimal.Decimal(value)
        ctx.rounding = decimal.ROUND_DOWN
        return round(d, decimals)

print(round_down(28.266, 2))  # 28.26

Which gives more sensible results for the rounding:

for i in range(0, 8):
    print(i, round_down(12.33333, i))
0 12
1 12.3
2 12.33
3 12.333
4 12.3333
5 12.33333
6 12.333330
7 12.3333300

As with Fraction a Decimal should be created from a string to avoid the intermediate inexact float. But different from Fraction the Decimal have limited precision, so for values with lots of significant figures it will also become inexact.

However "rounding down" is just one of the available options. The list of available rounding modes is extensive:

Rounding modes

decimal.ROUND_CEILING Round towards Infinity.

decimal.ROUND_DOWN Round towards zero.

decimal.ROUND_FLOOR Round towards -Infinity.

decimal.ROUND_HALF_DOWN Round to nearest with ties going towards zero.

decimal.ROUND_HALF_EVEN Round to nearest with ties going to nearest even integer.

decimal.ROUND_HALF_UP Round to nearest with ties going away from zero.

decimal.ROUND_UP Round away from zero.

decimal.ROUND_05UP Round away from zero if last digit after rounding towards zero would have been 0 or 5; otherwise round towards zero.

Community
  • 1
  • 1
MSeifert
  • 118,681
  • 27
  • 271
  • 293
  • @user2682863 Some of the solutions mentioned in my example are affected by floating point imprecision. But I noted that on each approach but the last two solutions with fraction and decimal aren't affected. – MSeifert Feb 01 '20 at 17:23
5

With Python 3.9, you can use quantize()

from decimal import *    
>>> Decimal('7.325').quantize(Decimal('.01'), rounding=ROUND_DOWN)
Decimal('7.32')

I tested the above answer and it worked. But I find this 1 liner is quite simple. So I also write my answer here.

Source: https://docs.python.org/3/library/decimal.html

Benny Chan
  • 195
  • 1
  • 13
3

simply try this:

import math
a = 28.266
print((math.floor(a * 100)) / 100.0)

Output:

28.26
Inconnu
  • 4,928
  • 2
  • 31
  • 41
2

here's a simple function that isn't affected by float precision errors

def truncate_float(n, places):
    return int(n * (10 ** places)) / 10 ** places

Tests:

>>> truncate_float(28.266, 3)
28.266
>>> truncate_float(28.266, 2)
28.26
>>> truncate_float(28.266, 1)
28.2
user2682863
  • 2,680
  • 1
  • 19
  • 34
0

There's an even simpler way of doing this generically, by subtracting a small quantity before rounding, like so:

a = 28.269
digits = 2
print(round(a - 0.5/10**digits, digits))

This is based on the intuition that one way of rounding a float to the nearest integer is by adding 0.5, then truncating. The above solution does the opposite by subtracting a half of the minimal 'tick' that the desired precision allows, then rounding.

guacamole
  • 21
  • 1
  • 5
  • If you use `digits = 3` then the result is `28.268` - which isn't correct – MSeifert Sep 29 '19 at 09:09
  • You're right, it's occasionally incorrect for certain values. Is this because round() uses the round half even mode? – guacamole Sep 29 '19 at 11:25
  • 1
    I think the difference is because floats are inexact, `a - 0.5/10**digits` is actually `28.268499999999999516830939683132...` for `digits=3`. However `28.269` is also not exactly representable as float: `28.268999999999998351540853036568...`. That's why my answer also shows how to use `Fraction` or `Decimal` to round the value, because fraction is "really exact" and decimal is almost always "exact enough" (at least if the input is given as string (not as float). – MSeifert Sep 29 '19 at 12:02