-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
Description
Bug report
Bug description:
My motivation for supplying a context arg is to try and cure some disastrous behavior of shift.
Consider this code:
from decimal import Decimal
d = Decimal("-79341.2900373")
print(f"{d.shift(10) = !s}")
print(f"{d.shift(-10) = !s}")
That code prints:
d.shift(10) = -793412900373000.0000000
d.shift(-10) = -0.0000079
The first result, for other = 10, is what I expect: the digits of d were shifted 10 places to the left of the decimal point.
The second result, for other = -10, already shows a problem: the digits of d were shifted 10 places to the right of the decimal point but then the 10 rightmost digits (3412900373) were dropped, which I consider to be bad.
Now rerun the above code but with d modified to have a lot more digits after the decimal point, say
d = Decimal("-79341.29003731915145181119441986083984375")
That code now prints:
d.shift(10) = -1.194419860839843750000000000E-8
d.shift(-10) = -1.91514518111944198E-18
These results are vastly different than what I expect.
From web searches, I am aware that this behavior occurs because of how Decimal does operations exactly and then rounds, drops digits, etc according to some context.
For example, this link describes similar unexpected behavior in Decimal.normalize.
The final answer in the link above by ghedsouza is to call normalize with a custom Context that ought to stop rounding.
I have verified (by throwing tons of randomly generated Decimals at it) that ghedsouza's custom Context seems to perfectly cure normalize.
Can ghedsouza's approach also cure defects in shift?
Consider this code, where I now call shift with a context arg equal to ghedsouza's Context:
from decimal import Context, Decimal, Inexact, MAX_EMAX, MAX_PREC, MIN_EMIN
CONTEXT_ROUND_NEVER = Context(
traps = [Inexact],
prec = MAX_PREC,
Emax = MAX_EMAX,
Emin = MIN_EMIN,
)
print(f"{CONTEXT_ROUND_NEVER = }")
d = Decimal("-79341.29003731915145181119441986083984375")
print(f"{d.shift(10, CONTEXT_ROUND_NEVER) = !s}")
print(f"{d.shift(-10, CONTEXT_ROUND_NEVER) = !s}")
That code prints:
CONTEXT_ROUND_NEVER = Context(prec=999999999999999999, rounding=ROUND_HALF_EVEN, Emin=-999999999999999999, Emax=999999999999999999, capitals=1, clamp=0, flags=[], traps=[Inexact])
d.shift(10, CONTEXT_ROUND_NEVER) = -793412900373191.51451811194419860839843750000000000
d.shift(-10, CONTEXT_ROUND_NEVER) = -0.00000793412900373191514518111944198
The d.shift(10) result is now perfect, exactly what I expect.
The d.shift(-10) result, while vastly improved, does have this defect: it dropped the final digits that should have been on the right end (6083984375).
As near as I can tell, the settings in that CONTEXT_ROUND_NEVER instance are so broad that this should NOT have happened: d.shift(-10) should have dropped no digits.
To me, it is as if Decimal.shift does not fully obey its context arg (at least for right shifts, negative values of other).
So, does shift have a bug?
Or (more likely) is there some subtle behavior in Decimal and/or Context which explains why this behavior is happening?
Final note.
According to its documentation, the Decimal.shift method ought to merely move the Decimal's digit about the decimal point (i.e. be equivalent to multiplying by a power of 10):
https://docs.python.org/3/library/decimal.html#decimal.Decimal.shift
There is no warning in that documentation about any of the completely unexpected behaviors described above.
But there needs to be.
CPython versions tested on:
3.12
Operating systems tested on:
Windows
Metadata
Metadata
Assignees
Labels
Projects
Status