9

Why does the following code:

from decimal import Decimal
result = Decimal('0') * Decimal('0.8881783462119193534061639577')
print(result)

return 0E-28 ?

I've traced it to the following code in the module:

if not self or not other:
    ans = _dec_from_triple(resultsign, '0', resultexp)
    # Fixing in case the exponent is out of bounds
    ans = ans._fix(context)
    return ans

The code appears to follow Decimal Arithmetic Specification, which doesn't explicitly suggest what to do when we multiply by zero, referring to 'special numbers' from another standard, which also doesn't specify what we do when we multiply an integer by zero :) So the decimal library does the thing that is explicitly specified:

  • The coefficient of the result, before rounding, is computed by multiplying together the coefficients of the operands.
  • The exponent of the result, before rounding, is the sum of the exponents of the two operands.
  • The sign of the result is the exclusive or of the signs of the operands.

Question: what is the need to return the coefficient and exponent (i.e, 0E-28) if one of the operands is a zero? We already know what that coefficient is when calling the multiplication function. Why not just return zero?

tonysepia
  • 3,340
  • 3
  • 27
  • 45

1 Answers1

3

Raymond Hettinger has given a comprehensive explanation at cpython github:

In Arithmetic Operations, the section on Arithmetic operations rules tells us:

Trailing zeros are not removed after operations.

There are test cases covering multiplication by zero. Here are some from multiply.decTest:

-- zeros, etc.
mulx021 multiply  0      0     ->  0
mulx022 multiply  0     -0     -> -0
mulx023 multiply -0      0     -> -0
mulx024 multiply -0     -0     ->  0
mulx025 multiply -0.0   -0.0   ->  0.00
mulx026 multiply -0.0   -0.0   ->  0.00
mulx027 multiply -0.0   -0.0   ->  0.00
mulx028 multiply -0.0   -0.0   ->  0.00
mulx030 multiply  5.00   1E-3  ->  0.00500
mulx031 multiply  00.00  0.000 ->  0.00000
mulx032 multiply  00.00  0E-3  ->  0.00000     -- rhs is 0
mulx033 multiply  0E-3   00.00 ->  0.00000     -- lhs is 0
mulx034 multiply -5.00   1E-3  -> -0.00500
mulx035 multiply -00.00  0.000 -> -0.00000
mulx036 multiply -00.00  0E-3  -> -0.00000     -- rhs is 0
mulx037 multiply -0E-3   00.00 -> -0.00000     -- lhs is 0
mulx038 multiply  5.00  -1E-3  -> -0.00500
mulx039 multiply  00.00 -0.000 -> -0.00000
mulx040 multiply  00.00 -0E-3  -> -0.00000     -- rhs is 0
mulx041 multiply  0E-3  -00.00 -> -0.00000     -- lhs is 0
mulx042 multiply -5.00  -1E-3  ->  0.00500
mulx043 multiply -00.00 -0.000 ->  0.00000
mulx044 multiply -00.00 -0E-3  ->  0.00000     -- rhs is 0
mulx045 multiply -0E-3  -00.00 ->  0.00000     -- lhs is 0

And this from the examples:

mulx053 multiply 0.9 -0 -> -0.0

In the Summary of Arithmetic section, the motivation is explained at a high level:

The arithmetic was designed as a decimal extended floating-point arithmetic, directly implementing the rules that people are taught at school. Up to a given working precision, exact unrounded results are given when possible (for instance, 0.9 รท 10 gives 0.09, not 0.089999996), and trailing zeros are correctly preserved in most operations (1.23 + 1.27 gives 2.50, not 2.5). Where results would exceed the working precision, floating-point rules apply.

More detail in given in the FAQ section Why are trailing fractional zeros important?.

tonysepia
  • 3,340
  • 3
  • 27
  • 45