views:

256

answers:

6

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

A: 

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
perfect :) --- completing character limit ---
Gunjan
@Gunjan, if it's perfect, why not accept it?!
Alex Martelli
@Alex sorry had to leave before 6 minute timeout. Thanks for the reminder
Gunjan
-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
A: 
>>> decimal.Context(rounding=decimal.ROUND_CEILING).quantize(
...   decimal.Decimal(800000000000000000001)/100000000000000000000, 0)
Decimal('9')
Ignacio Vazquez-Abrams
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
A: 
def decimal_ceil(x):
    int_x = int(x)
    if x - int_x == 0:
        return int_x
    return int_x + 1
fviktor
+2  A: 
x = decimal.Decimal('8.00000000000000000000001')
with decimal.localcontext() as ctx:
    ctx.prec=100000000000000000
    ctx.rounding=decimal.ROUND_CEILING
    y = x.to_integral_exact()
lojack
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
A: 

I'm sure there are library functions to do this (as Ignacio Vazquez-Abrams points out), but since you haven't accepted any answer, I got the impression that you wanted to see how it's done - your own version of ceil. So here is one possible solution:

def ceil(d):
    return [eval("int(d) + [0,1][int(bool(d-int(d)))]"), eval("int(d)")][int(d<0)]

Hope this helps

inspectorG4dget
int() and bool() are not library functions?? A simpler `a priori` explanation for not accepting any answer is that the OP has been on SO for only 13 days and this is the first question (and so may need telling/reminding to accept answers) and it was asked only 5 hours ago and the OP may be waiting for those in other TZs to reply or may now be asleep and will check answers at breakfast-time ... `a fortiori` however the OP commented "Perfect" to Matthew's answer (the first answer) which was rather nuts'n'boltsy so I'd be inferring that he's already seen "how it's done".
John Machin
This fails for negative numbers: `ceil(-2.3)` --> `-1`.
Mark Dickinson
+1  A: 

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.

Mark Dickinson