12

I've got a python Decimal (a currency amount) which I want to round to two decimal places. I tried doing this using the regular round() function. Unfortunately, this returns a float, which makes it unreliable to continue with:

>>> from decimal import Decimal
>>> a = Decimal('1.23456789')
>>> type(round(a, 2))
<type 'float'>

in the decimal module, I see a couple things in relation to rounding:

  • ROUND_05UP
  • ROUND_CEILING
  • ROUND_DOWN
  • ROUND_FLOOR
  • ROUND_HALF_DOWN
  • ROUND_HALF_EVEN
  • ROUND_HALF_UP
  • ROUND_UP
  • Rounded

I think that none of these actually give what I want though (or am I wrong here?).

So my question: does anybody know how I can reliably round a Python Decimal to 2 decimal places so that I have a Decimal to continue with? All tips are welcome!

kramer65
  • 50,427
  • 120
  • 308
  • 488
  • 2
    Wouldn't it make more sense to use integers (cents instead of dollars)? That would also be faster. – Tim Pietzcker May 10 '14 at 16:09
  • To set the precision of a decimal number: `decimal.getcontext().prec=2`. – ooga May 10 '14 at 16:12
  • 2
    @ooga: That deals with the number of significant digits, not the number of decimal digits and is therefore unsuitable (it turns `12345678` into `1.2E+7`, but only after doing calculations on it, not right after definition). – Tim Pietzcker May 10 '14 at 16:17
  • @TimPietzcker I see. In that case I agree that an integer is best. – ooga May 10 '14 at 16:18
  • 4
    It's worth noting that `round` does return a `Decimal` instance directly with Python 3; it's only on Python 2 that it converts to `float`. Even then, though, you'll still need `quantize` if you want anything other than the default `ROUND_HALF_EVEN` rounding mode. – Mark Dickinson May 11 '14 at 11:01

8 Answers8

18

You could use the quantize() method:

>>> import decimal
>>> decimal.getcontext().prec = 20
>>> a = decimal.Decimal(321.12345)
>>> a
Decimal('321.12344999999999117790139280259609222412109375')
>>> TWO_PLACES = decimal.Decimal("0.01")
>>> a.quantize(TWO_PLACES)
Decimal('321.12')

The Python docs have a nice recipe how to build a string representation of currency values using Decimals and .quantize().

Tim Pietzcker
  • 328,213
  • 58
  • 503
  • 561
13

Since Python 3.3 you can use round() with a Decimal and it will return you a Decimal:

>>> from decimal import Decimal
>>> round(Decimal('3.14159265359'), 3)
Decimal('3.142')

See details in this answer.

ericbn
  • 10,163
  • 3
  • 47
  • 55
  • round(Decimal(0.5), 0) returns 0 ? – Nimitz14 Jan 10 '23 at 00:59
  • 1
    `round` uses ROUND_HALF_EVEN, where even numbers (including 0) round down, and odd numbers round up. This helps avoid a slight skewing of results when dealing with large data sets. – jobu1342 Feb 21 '23 at 01:14
7

The first one you tried worked just fine for me.

import decimal

type(round(decimal.Decimal('1.23456789'),2))

<class 'decimal.Decimal'>

VP.
  • 15,509
  • 17
  • 91
  • 161
BattleDrum
  • 798
  • 7
  • 13
  • 1
    `round` will make the result an object of `float` class – Rohan Nov 21 '19 at 11:21
  • 1
    I just tested it in Python version 3.8 and I see that you're right. I guess that was changed in a version of Python released after I tested this. – kramer65 Feb 21 '22 at 11:57
  • `sys.version_info(major=3, minor=8, micro=5, releaselevel='final', serial=0)` gives `` for `print(type(round(decimal.Decimal(1.1233999999999999541699935434735380113124847412109375), 2)))` – Uroš Dukanac Jul 13 '22 at 14:09
1

This has came to my mind:

import decimal
decimal.Decimal(str(round(a, 2)))

but I don't know how fast it is.

Tamil Selvan S
  • 562
  • 8
  • 25
Josip Grggurica
  • 421
  • 4
  • 12
1

If you are using Jupyter Notebook, just add the magic function %precision 2 for 2 decimal and so forth.

But if your values are not float() type then you might use the following:

 from decimal import getcontext, Decimal
 getcontext().prec = 2
 # for example, take integer value
 mynum = 2
 type(mynum) # Int
 print round(Decimal(mynum), 2) # 10.0
Manoj Kumar
  • 5,273
  • 1
  • 26
  • 33
  • 1
    In Jupyter Notebook, `%precision` appears to only set pretty-print precision for float values, not Decimal values. – Gary Mar 23 '19 at 15:36
  • 2
    Also, `getcontext().prec` = 2 will cause Decimal operations to retain 2 significant digits, not 2 significant decimal places. E.g. `Decimal('1') * Decimal('123')` gives `Decimal('1.2E+2')`. – Gary Mar 23 '19 at 15:37
0

Not sure why they don't have a built-in function, but I made one. I know it's not technically "trueRound" but you can change that if you want. Tested on python 3.9

    def trueRound(num: float or str, decimalPlaces: int) -> float:
        a = Decimal(str(num))
        places_str = "0."
        for i in range(decimalPlaces - 1):
            places_str += "0"
        places_str += "1"
        PLACES = decimal.Decimal(places_str)
        result = a.quantize(PLACES)
        return float(result)
  • 1
    Sorry, you have to do: import decimal from decimal import Decimal. I know it's not great to have one Decimal() and another decimal.Decimal but I'm not sure if I can edit my answer. – countinglambdastosleep Oct 14 '21 at 21:32
0

More Pythonic code based on the answer of @countinglambdastosleep

from decimal import Decimal, ROUND_HALF_EVEN

def decimalize(n, prec=8):
    n = n if isinstance(n, Decimal) else Decimal(n)
    fmt = ".{}1".format("0" * (prec - 1))
    return n.quantize(Decimal(fmt), rounding=ROUND_HALF_EVEN)

Why ROUND_HALF_EVEN? You may find answers here

Do Anh Tu
  • 11
  • 1
  • 2
0

@Do Anh Tu, nice answer...just one minor change so as to handle float inputs as well, apart from Decimal and string inputs, can change to Decimal(n) to Decimal(str(n)), following being the overall code:

from decimal import Decimal, ROUND_HALF_EVEN

def decimalize(n, prec=8):
    n = n if isinstance(n, Decimal) else Decimal(str(n))
    fmt = ".{}1".format("0" * (prec - 1))
    return n.quantize(Decimal(fmt), rounding=ROUND_HALF_EVEN)
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31268898) – Tugrul Ates Mar 15 '22 at 01:54