This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Decimal class error messages for integer division aren't good
Type: enhancement Stage: resolved
Components: Documentation, Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: wont fix
Dependencies: 19232 Superseder:
Assigned To: skrah Nosy List: docs@python, facundobatista, leewz, mark.dickinson, rhettinger, skrah, tim.peters
Priority: low Keywords:

Created on 2014-04-15 00:02 by leewz, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (13)
msg216253 - (view) Author: Franklin? Lee (leewz) Date: 2014-04-15 00:02
Python's `decimal.Decimal` doesn't seem to like taking modulo or intdiv of large Decimals by integers (where "large" depends on how many digits are internally stored).

    >>> from decimal import *
    >>> getcontext().prec
    28
    >>> Decimal(10**29)%1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    decimal.InvalidOperation: [<class 'decimal.DivisionImpossible'>]
    >>> getcontext().prec=50
    >>> Decimal(10**29)%1
    Decimal('0')

Same for `Decimal(10**29)//1`

This is a logical problem: "Alice has a 100-digit number which begins with 1543256. What is the last digit?"

But I found the error hard to understand. Searching for "DivisionImpossible" didn't turn up anything immediate (it wasn't added to the official docs?). I was most of the way through writing out a question to StackOverflow when it clicked. (Also, why is it an InvalidOperation that holds an exception as a message? Never saw that before.)

Suggestions:
- Improve docs with further examples. The examples of InvalidOperation are logical MATH errors (e.g. division by 0), not logical PROGRAMMING errors.
- Uh, maybe the error message could be changed to something like, "The answer you seek lies beyond the mantissa." Or more sanely, "Not enough precision to compute the answer." Or something else that hints to me to look into precision.
msg216492 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 16:38
It is hard to get fine grained error messages in _decimal, since
the errors come from libmpdec.  A clean solution would require
changes to libmpdec, and I'm reluctant to do that right now.

It is certainly possible to document DivisionImpossible etc.
msg216496 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 16:48
Meanwhile, the pure Python decimal versions prior to Python 3.2
have better error messages.

Right now in Python 3.3+ it is hard to import the Python version
without going into contortions, but that may be fixed in #19232.
msg216510 - (view) Author: Franklin? Lee (leewz) Date: 2014-04-16 17:35
Fine grained? Do you mean that the error can't be distinguished from other such errors? Or that it's difficult to attach the message to DivisionError? I thought DivisionError was always about precision.

I looked up the error in libmpdec:
"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]." (http://speleotrove.com/decimal/daexcep.html)

Now I'm more confused. Though it mentions precision, it is talking about the *result's* precision being too large (something which shouldn't happen with Python's unbounded ints, right?), rather than being unable to give a sane answer due to not having *enough* digits. That's also what the 2.7 error is:
    decimal.InvalidOperation: quotient too large in //, % or divmod

I'm very much content with documenting it, but if possible, I'd like to understand whether this is an issue to take up with libmpdec.

P.S.: As a side-note to others, Python floats allows float%int even when precision isn't high enough, and seems to always returns 0.0 with no warning. So behavior is inconsistent, if that's important to anyone here.
msg216521 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 17:55
My apologies if that wasn't clear: "fine grained" refers to the exception
messages.  A function can raise InvalidOperation for different reasons.

decimal.py gives a specific error message in each case. libmpdec just
signals the standard conforming InvalidOperation.

The relevant passage from the standard is here:

http://speleotrove.com/decimal/daops.html#refremain

  "This operation will fail under the same conditions as integer division."

decimal.py, decNumber and libmpdec all do the same thing, so there is no
libmpdec issue other than that the error *messages* could be improved.

I fully understand if you find the behavior surprising (after all the remainder
fits in the precision), but as long as we follow the standard we can't change
that.
msg216531 - (view) Author: Franklin? Lee (leewz) Date: 2014-04-16 18:04
Nah. I found it surprising at first, but like I said, it's like the computer is given the first 28 digits of a number and then asked to figure out the 30th digit.

What I'm confused about is how it fits the definition of "division impossible" given by libmpdec's docs (about the result size), and whether you're saying it's difficult to translate the `[<class 'decimal.DivisionImpossible'>]` part to an error message.
msg216565 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 19:46
In the case of DivisionImpossible it would actually be possible to use
the error message 'quotient too large in //, % or divmod'.

But that's just one condition.  In the case of InvalidOperation there
are something like 30 different error messages.
msg216573 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2014-04-16 20:03
> It is certainly possible to document DivisionImpossible etc.

This should not be done.  We've made strong efforts to not extend the Decimal Arithmetic Specification.

Also, the API already suffers from high complexity.  Adding more exceptions to the mix just makes the module harder to learn.

Instead, you are welcome to add more specific text messages to the existing (and standards compliant) exceptions:

   raise InvalidOperation("Not enough precision to compute the answer.")

> I found it surprising at first, but like I said, it's like 
> the computer is given the first 28 digits of a number and 
> then asked to figure out the 30th digit.

People are always surprised when their mental model conflicts with the actual design model.  That doesn't mean the implementation should change.

There may be a documentation issue, but then again, people who are "surprised" usually haven't studied the docs in detail.

My overall sense for this bug report is that there isn't a real problem that needs to be solved.  AFAICT, this particular "confusion" has never arisen before (i.e. bug reports, questions in python classes, posts on stackoverlow, blog posts, etc).  Likewise, the issue does not seem to have ever arisen in the many other non-Python implementations of the decimal spec.

I recommend that this either be closed or be limited to tweaking some of the error messages for existing exceptions.
msg216582 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 20:28
Raymond Hettinger <report@bugs.python.org> wrote:
> > It is certainly possible to document DivisionImpossible etc.
> 
> This should not be done.  We've made strong efforts to not extend the Decimal Arithmetic Specification.

The exceptions are already there:

Python 2.7.3 (default, Mar 13 2014, 11:03:55)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import decimal
>>> print(decimal.DivisionImpossible.__doc__)
Cannot perform the division adequately.

    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].

The specification distinguishes between "conditions" and "signals". It is
true that the conditions are not technically raised, but they are very
much "subclasses" of InvalidOperation.

Cowlishaw himself makes the distinction between InvalidOperation
and IEEEInvalidOperation. The latter groups all conditions together:

#define DEC_IEEE_754_Invalid_operation (DEC_Conversion_syntax |     \
                                        DEC_Division_impossible |   \
                                        DEC_Division_undefined |    \
                                        DEC_Insufficient_storage |  \
                                        DEC_Invalid_context |       \
                                        DEC_Invalid_operation)

So while I don't particularly care whether we document the conditions or
not, I don't think doing so would extend the specification.
msg216589 - (view) Author: Franklin? Lee (leewz) Date: 2014-04-16 20:52
Total list of issues now:
- Error message for `DivisionImpossible` is
      [<class 'decimal.DivisionImpossible'>]
  instead of an actual error message.
- `decimal.DivisionImpossible.__doc__` is empty.
- Calling `help(decimal.DivisionImpossible)` turns up nothing useful.

I checked all of these just now on my 3.2.3 (Windows). These issues are a CHANGE from 3.2 to 3.3. For example:
- Old error:
  decimal.InvalidOperation: quotient too large in //, % or divmod
- New error:
  InvalidOperation: [<class 'decimal.DivisionImpossible'>]

I assume that the issues also apply to the other InvalidOperation types. So I guess what I'm really asking for is to pull forward the 3.2 docs and messages.

> That doesn't mean the implementation should change.

Er, I was explaining why it wasn't really surprising once I thought about it.

> There may be a documentation issue, but then again, people who are "surprised" usually haven't studied the docs in detail.

To clarify:
- I looked at the Decimal docs
    - skimmed for references to modulo and division
    - looked for `DivisionImpossible`
- Then looked on Google for '"DivisionImpossible" Python'.
- Experimented with changing precision and fooling around with bigger and smaller numbers, and realized why the operation was logically impossible.
- Came here, posted, got a response that it was a flag raised by libmpdec.

I'm not asking for a change in behavior. I did point out that it was inconsistent with regular floats, in case consistency with Pyfloats was a thing that mattered. But I don't care about that. I only worry about anyone else who would use Decimal coming across the same unhelpful error.

> AFAICT, this particular "confusion" has never arisen before (i.e. bug reports, questions in python classes, posts on stackoverlow, blog posts, etc).  Likewise, the issue does not seem to have ever arisen in the many other non-Python implementations of the decimal spec.

I agree it'd be (very) rare, but part of the reason why it might not appear online is that the issues at the top of this email are relatively new (3.3).
msg216615 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 22:45
leewz <report@bugs.python.org> wrote:
> - Error message for `DivisionImpossible` is
>       [<class 'decimal.DivisionImpossible'>]
>   instead of an actual error message.

No, the error message for the *signal*  that is raised (InvalidOperation) lists
the *condition* that triggered the signal (DivisionImpossible).

I followed the recommendation at:

http://speleotrove.com/decimal/daexcep.html#refexcep

"It is recommended that implementations distinguish the different conditions
 listed above, and also provide additional information about exceptional
 conditions where possible (for example, the operation being attempted and
 the values of the operand or operands involved)."

Distinguishing the conditions is easy, adding additional information in
all cases would require changes to libmpdec.
msg216622 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-04-16 23:17
The idea behind the list as the exception message is this:  If multiple
conditions would have raised the signal they are all listed, while the
"highest ranking" signal is the one that is ultimately raised.

>>> from decimal import *
>>> c = getcontext()
>>> for v in c.traps:
...     c.traps[v] = True
... 
>>> 
>>> Decimal(8) ** 1000000000000000
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
decimal.Overflow: [<class 'decimal.Overflow'>, <class 'decimal.Inexact'>, <class 'decimal.Rounded'>]

Exception precedence is listed here at the bottom of the page:

http://speleotrove.com/decimal/daexcep.html
msg229302 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2014-10-14 15:20
I guess there's not much to be done here.
History
Date User Action Args
2022-04-11 14:58:01adminsetgithub: 65426
2014-10-14 15:20:01skrahsetstatus: open -> closed
resolution: wont fix
messages: + msg229302

stage: resolved
2014-04-16 23:17:35skrahsetmessages: + msg216622
2014-04-16 22:45:52skrahsetmessages: + msg216615
2014-04-16 20:52:28leewzsetmessages: + msg216589
2014-04-16 20:48:18mark.dickinsonsetnosy: + mark.dickinson
2014-04-16 20:28:42skrahsetmessages: + msg216582
2014-04-16 20:03:30rhettingersetpriority: normal -> low
versions: + Python 3.5, - Python 3.3, Python 3.4
nosy: + tim.peters, rhettinger, facundobatista

messages: + msg216573
2014-04-16 19:46:18skrahsetmessages: + msg216565
2014-04-16 18:04:46leewzsetmessages: + msg216531
2014-04-16 17:55:08skrahsetmessages: + msg216521
2014-04-16 17:35:52leewzsetmessages: + msg216510
2014-04-16 16:48:14skrahsetdependencies: + Speed up _decimal import
messages: + msg216496
2014-04-16 16:38:51skrahsetmessages: + msg216492
2014-04-15 09:42:52skrahsetassignee: docs@python -> skrah

nosy: + skrah
2014-04-15 00:02:39leewzcreate