16

Is there a way to get the ceil of a high precision Decimal in python?

>>> import decimal;
>>> decimal.Decimal(800000000000000000001)/100000000000000000000
Decimal('8.00000000000000000001')
>>> math.ceil(decimal.Decimal(800000000000000000001)/100000000000000000000)
8.0

math rounds the value and returns non precise value

Gunjan
  • 1,177
  • 2
  • 11
  • 22
  • I'm new to python, just started out yesterday in fact. Stumbled upon this problem in my second practice program (the first was of course the obligatory "print 'Hello, World!';"). so, I'm finding it difficult to judge the best answer to this. The decimal.Context solution by Matthew Flaschen worked in my particular case. But I'd like others to upvote the best solution (also would be helpful for newbies like me if you can explain why a certain approach works better) and I'll come back and accept. – Gunjan May 10 '10 at 08:50

6 Answers6

34

The most direct way to take the ceiling of a Decimal instance x is to use x.to_integral_exact(rounding=ROUND_CEILING). There's no need to mess with the context here. Note that this sets the Inexact and Rounded flags where appropriate; if you don't want the flags touched, use x.to_integral_value(rounding=ROUND_CEILING) instead. Example:

>>> from decimal import Decimal, ROUND_CEILING
>>> x = Decimal('-123.456')
>>> x.to_integral_exact(rounding=ROUND_CEILING)
Decimal('-123')

Unlike most of the Decimal methods, the to_integral_exact and to_integral_value methods aren't affected by the precision of the current context, so you don't have to worry about changing precision:

>>> from decimal import getcontext
>>> getcontext().prec = 2
>>> x.to_integral_exact(rounding=ROUND_CEILING)
Decimal('-123')

By the way, in Python 3.x, math.ceil works exactly as you want it to, except that it returns an int rather than a Decimal instance. That works because math.ceil is overloadable for custom types in Python 3. In Python 2, math.ceil simply converts the Decimal instance to a float first, potentially losing information in the process, so you can end up with incorrect results.

Mark Dickinson
  • 29,088
  • 9
  • 83
  • 120
  • 1
    Just for completeness: in python 2.x `math.ceil` doesn't work as expected: it converts decimal to float first. Because of that `int(ceil(D('1.0000000000000001')))` evaluates to 1 in python 2.x and to 2 in python 3.x – Antony Hatchkins May 12 '15 at 11:07
  • 1
    @AntonyHatchkins: Thanks, good point. I've edited that information into the answer. – Mark Dickinson May 12 '15 at 11:57
6
x = decimal.Decimal('8.00000000000000000000001')
with decimal.localcontext() as ctx:
    ctx.prec=100000000000000000
    ctx.rounding=decimal.ROUND_CEILING
    y = x.to_integral_exact()
Robert Clark
  • 488
  • 3
  • 5
  • 6
    This is good, but there's no need to change the context precision here. `to_integral_exact` also takes a `rounding` argument, so you can avoid messing with the context altogether. – Mark Dickinson May 09 '10 at 10:04
4

You can do this using the precision and rounding mode option of the Context constructor.

ctx = decimal.Context(prec=1, rounding=decimal.ROUND_CEILING)
ctx.divide(decimal.Decimal(800000000000000000001), decimal.Decimal(100000000000000000000))

EDIT: You should consider changing the accepted answer.. Although the prec can be increased as needed, to_integral_exact is a simpler solution.

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • perfect :) --- completing character limit --- – Gunjan May 08 '10 at 23:01
  • @Alex sorry had to leave before 6 minute timeout. Thanks for the reminder – Gunjan May 09 '10 at 09:45
  • -1. This doesn't generalize well; it only happens to work in this case because the result is in the range [1, 10]. Try the same calculation with Decimal(123)/Decimal(10), for example, and you'll get a result of `Decimal('2E+1')`. – Mark Dickinson May 09 '10 at 10:14
0

Just use potency to make this. import math

def lo_ceil(num, potency=0): # Use 0 for multiples of 1, 1 for multiples of 10, 2 for 100 ...
      n = num / (10.0 ** potency)
      c = math.ceil(n)
      return c * (10.0 ** potency)

lo_ceil(8.0000001, 1) # return 10
0
>>> decimal.Context(rounding=decimal.ROUND_CEILING).quantize(
...   decimal.Decimal(800000000000000000001)/100000000000000000000, 0)
Decimal('9')
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Note that this solution has problems for `Decimal` instances with large value: e.g., if you try `c.quantize(decimal.Decimal('1e100'), 1)` with your context `c`, you'll get an `InvalidOperation` exception. – Mark Dickinson May 09 '10 at 10:34
0
def decimal_ceil(x):
    int_x = int(x)
    if x - int_x == 0:
        return int_x
    return int_x + 1
fviktor
  • 2,861
  • 20
  • 24