Multiplication of Decimal with {float/complex/Fraction}

current scenario -

from decimal import Decimal
Decimal('0.5') * 2.1

gives,

TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'float'
Decimal('0.5') * complex(1 + 2j)

gives,

TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'complex'
from fractions import Fraction
Decimal('0.5') * Fraction(1, 2)

gives,

TypeError: unsupported operand type(s) for *: 'decimal.Decimal' and 'Fraction'

expected scenario -

  1. all three work

note -

  1. Fraction * {Fraction/float/complex}, float * {complex/float}, complex * complex are all valid
  2. only the Decimal type appears to have these invalid multiplications

The documentation for the decimal module does nowhere suggest that the Decimal type supports arithmetic operations between Decimal and the builtin numeric types, see https://docs.python.org/3/library/decimal.html.

Decimal is deliberately inoperable with other non-integer numeric types in many situations due to the high likelihood of surprising results. For example, what would you expect the result of Decimal('0.1') * 0.1 to be? I would naively expect Decimal('0.01'), but if you force things by casting the float to Decimal (as in Decimal('0.1') * Decimal(0.1), note the lack of quotes in the second term), the result is actually Decimal('0.01000000000000000055511151231'). Consider also the difference between Fraction(1, 3).as_integer_ratio() and (Decimal(1) / Decimal(3)).as_integer_ratio(), and the fact that Decimal does not have its own support for the complex plane.

In short, Decimal refuses the temptation to guess what you want and instead forces you to deal with the inexact conversion of other types to Decimal yourself.

2 Likes

What would be the result of these examples? Justify your answer.

Decimal('0.5') * 2.1
Decimal('0.5') * complex(1 + 2j)
Decimal('0.5') * Fraction(1, 2)

Its not enough to say that these should “work” unless you can explain what you mean by “work”.

based on my inspection,

print(Fraction(1, 2) * 2.1)
print(Fraction(1, 2) * complex(1 + 2j))
print(Fraction(1, 2) * Fraction(1, 2))
print(Decimal('0.5') * 2)

give,

1.05
(0.5+1j)
1/4
1.0

similarly,

print(Decimal('0.5') * 2.1)
print(Decimal('0.5') * complex(1 + 2j))
print(Decimal('0.5') * Fraction(1, 2))

should give,

1.05
(0.5+1j)
0.25 # or it could be made 
     # Decimal * Fraction gives 0.25, 
     # Fraction * Decimal gives 1/4

if we print the output without using print, then,

(Fraction(1, 2) * 2.1), (Fraction(1, 2) * complex(1 + 2j)), \
(Fraction(1, 2) * Fraction(1, 2))

gives,

(1.05, (0.5+1j), Fraction(1, 4))

and,

(Decimal('0.5') * 2)

gives,

Decimal('1.0')

similarly,

((Decimal('0.5') * 2.1), Decimal('0.5') * Fraction(1, 2))

should give,

(Decimal('1.05'), Decimal('0.25'))

plus,

Fraction(1, 2) * Decimal('0.5')

should give,

Fraction(1, 4)

for complex numbers it appears there is nothing like,

Decimal('1.2 + 2.1j')

so,

Decimal('0.5') * complex(1 + 2j)

is a bit confusing, but one possibility is to output,

(0.5 + 1j)

just like it works for Fraction

based on some further inspection, I noted down the types obtained after performing this multiplication for the valid cases,

arg1     | arg2     | result
-------------------------------
complex  | complex  | complex
complex  | float    | complex
complex  | Fraction | complex
complex  | int      | complex
Decimal  | Decimal  | Decimal
Decimal  | int      | Decimal
float    | float    | float
float    | Fraction | float
float    | int      | float
Fraction | Fraction | Fraction
Fraction | int      | Fraction
int      | int      | int

just like float * Fraction returns an object of type float, and complex * Fraction returns an object of type complex, the same could be true for {float/complex} * Decimal