rb612
rb612

Reputation: 5563

Math.pow vs ** casted as a Decimal in Python?

So I was writing a simple script to demonstrate geometric series convergence.

from decimal import *
import math
initial = int(input("a1? "))
r = Decimal(input("r? "))
runtime = int(input("iterations? "))

sum_value=0
for i in range(runtime):
   sum_value+=Decimal(initial * math.pow(r,i))
   print(sum_value)

When I use values such as:

a1 = 1
r = .2
iterations = 100000

I get the convergence to be 1.250000000000000021179302083

When I replace the line:

sum_value+=Decimal(initial * math.pow(r,i))

With:

sum_value+=Decimal(initial * r ** i)

I get a more precise value, 1.250000000000000000000000002

What exactly is the difference here? From my understanding, it has to do with math.pow being a floating point operation, but I would just think that ** is syntactic sugar for the math power function. If they are indeed different, then why with a precision value of 200, when inputting the following to IDLE:

>>> Decimal(.8**500)
Decimal('3.50746621104350087215129555150772856244326043764431058846880005304485310211166734705824986213804838358790165633656170035364028902957755917668691836297512054443359375E-49')
>>> Decimal(math.pow(.8,500))
Decimal('3.50746621104350087215129555150772856244326043764431058846880005304485310211166734705824986213804838358790165633656170035364028902957755917668691836297512054443359375E-49')

They seem to be exactly the same. What is happening here?

Upvotes: 2

Views: 1035

Answers (1)

JohanL
JohanL

Reputation: 6891

The difference is, as you imply, that math.pow() converts the inputs to floats as stated in the documentation: "Unlike the built-in ** operator, math.pow() converts both its arguments to type float."

Therefore math.pow() also delivers a float as answer, independently of whether the input is Decimal (or int) or whatever. When using numbers that are not exactly representable as a float(but is as Decimal) you are likely to get a more precise answer using the ** operator.

This explains why your loop gives a more exact result in case of using ** since you are working with Decimal numbers raised to an integer. In the second case, you are inadvertently using floats for both calculations and then converting the result to Decimal when the operation is already executed. If you instead work with explicit Decimal values you will see the difference:

>>> Decimal('.8')**500
Decimal('3.507466211043403874762758796E-49')
>>> Decimal(math.pow(Decimal('.8'), 500))
Decimal('3.50746621104350087215129555150772856244326043764431058846880005304485310211166734705824986213804838358790165633656170035364028902957755917668691836297512054443359375E-49')

Thus, in the second case, the Decimal value is automatically casted to a float and the result is the same as for your example above. In the first case, however, the calculation is executed in the Decimal domain and yields a slightly different result.

Upvotes: 3

Related Questions