Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

decimal.Decimal(n) % 1 returns InvalidOperation, DivisionImpossible for all n >= 100

I am using Decimal objects in a django app, and found this strange error:

ipdb> decimal.Decimal(10) % 1
    Decimal('0')
ipdb> decimal.Decimal(100) % 1
    *** decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]
ipdb> decimal.Decimal(150) % 1
    *** decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]
ipdb> decimal.Decimal(79) % 1
    Decimal('0')
ipdb> decimal.Decimal(100.1) % 2
    Decimal('0.10')
ipdb> decimal.Decimal(1000) % 2
    *** decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]

Even more mysteriously, this doesn't happen in ipython until the numbers get very large:

In [23]: decimal.Decimal(10**27) % 1
Out[23]: Decimal('0')

In [24]: decimal.Decimal(10**28) % 1
---------------------------------------------------------------------------
InvalidOperation                          Traceback (most recent call last)
<ipython-input-24-6ceaef82d283> in <module>()
----> 1 decimal.Decimal(10**28) % 1

InvalidOperation: [<class 'decimal.DivisionImpossible'>]

Note that the error is not confined to ipdb: I discovered this because Decimal(380) % 1 was breaking my django app.

The documentation describing this error says:

Division impossible

This occurs and signals invalid-operation if the integer result of a divide-integer or remainder operation had too many digits (would be longer than precision). The result is [0,qNaN].

Any ideas?

like image 871
mavix Avatar asked Aug 12 '16 18:08

mavix


People also ask

What does'Division impossible'mean in decimal?

Decimal is based on the Decimal Arithmetic specification. You can see here that "Division impossible" means that the integer result of a divide-integer or remainder operation had too many digits (would be longer than precision). This precision is something you can adjust:

Why do we use decimal values instead of floating point numbers?

This error occurs because of the approximations in floating point numbers due to which sum of 1.2 and 2.2 is not equal to 3.4. So, In cases where decimal values need to be compared, we should use the decimal module instead of floating point numbers. This can be more clear from the following example.

What is decimal in Python?

/Decimal Module in Python Decimal Module in Python Author: Aditya Raj Last Updated: June 24, 2021 Python has numeric data typeslike int, float and complex numbers but due to the machine dependent nature of floating point numbers, we need a more precise data type for calculations which demand high precision.

Why do we need machine independent data type for decimal numbers?

Therefore, For such applications, we need a machine independent data type to implement decimal numbers which has been implemented using the decimal module in python. Also, decimal module has implementation of decimal number for a precision of upto 28 decimal digits while floating point numbers have only a precision of upto 18 digits.


1 Answers

I think I figured it out.

Looking at the source code, I found this:

# catch most cases of large or small quotient
expdiff = self.adjusted() - other.adjusted()
if expdiff >= context.prec + 1:
    # expdiff >= prec+1 => abs(self/other) > 10**prec
    return context._raise_error(DivisionImpossible)
if expdiff <= -2:
    # expdiff <= -2 => abs(self/other) < 0.1
    ans = self._rescale(ideal_exponent, context.rounding)
    return ans._fix(context)

And in my django app, there's an adjustment to the prec:

decimal.getcontext().prec = 2

This still looks slightly wrong to me, because:

In [39]: decimal.getcontext().prec + 1
Out[39]: 3

In [40]: decimal.Decimal(100).adjusted() - decimal.Decimal(0).adjusted()
Out[40]: 2

And so it still looks like 100 is within the bounds of what it's checking for (that is, 2 < 3), but I am pretty confident this is the source of the problem. If anyone can illuminate for me why the library does this, I would love to understand it better.

like image 64
mavix Avatar answered Oct 17 '22 01:10

mavix