Python Concepts/Using Python's Decimal module
Objective
[edit | edit source]
|
Lesson
[edit | edit source]
Calculations using Python's Decimal module can be performed with (almost) any precision selectable by the user. Unfortunately many functions which you might expect to find in the module don't exist, for example, trigonometric functions and functions that manipulate complex numbers. For these functions you have to find them elsewhere or write them for yourself. Here are some examples that highlight the power of Python's Decimal Module. Conversion to Decimal object[edit | edit source]Several different objects are convertible to Decimal object. Type int converts exactly: >>> from decimal import *
>>> d1 = Decimal(12345678901123456789011234567890112345678901123456789011234567890112345678901);d1
Decimal('12345678901123456789011234567890112345678901123456789011234567890112345678901')
>>>
Precision is not applied when the Decimal object is initialized or displayed. Default precision of 28 is applied when the Decimal object is used: >>> d1 + 0; d1 - 0; +d1; -d1
Decimal('1.234567890112345678901123457E+76')
Decimal('1.234567890112345678901123457E+76')
Decimal('1.234567890112345678901123457E+76')
Decimal('-1.234567890112345678901123457E+76')
>>>
Although conversion is not necessary, Decimal object converts exactly. >>> d2 = Decimal(d1) ; d2
Decimal('123456789011234567890112345678901123456789011234567890112345678901')
>>>
Conversion from float to Decimal is tricky: >>> f1 = 3.14 ; f1
3.14
>>> d3 = Decimal( f1 ) ; d3 ; +d3
Decimal('3.140000000000000124344978758017532527446746826171875')
Decimal('3.140000000000000124344978758')
>>>
Conversion from float is accurate if correct precision for floats is applied. >>> getcontext().prec = 14
>>> f1 = 1.13-1.1 ; f1
0.029999999999999805
>>> Decimal(f1); +Decimal(f1);
Decimal('0.029999999999999804600747665972448885440826416015625')
Decimal('0.030000000000000')
>>>
Also, conversion from float is accurate if float is correctly formatted. >>> f1 = 1.13-1.1 ; f1
0.029999999999999805
>>> Decimal( '{:1.15f}'.format(f1) )
Decimal('0.030000000000000')
>>>
For simple, accurate conversion to Decimal, convert float to str first: >>> f1 = 3.14 ; str(f1) ; Decimal(str(f1))
'3.14'
Decimal('3.14')
>>>
>>> Decimal( '{}'.format(f1) )
Decimal('3.14')
>>>
Conversion from complex to Decimal: >>> Decimal(3+4j)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: conversion from complex to Decimal is not supported
>>>
Conversion from str representing number to Decimal is accurate. >>> Decimal('1234.5678901e3')
Decimal('1234567.8901')
>>>
eval() is more forgiving than Decimal(). >>> s1
' - 3456e-3 '
>>> Decimal(s1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>]
>>> Decimal( str(eval(s1)) )
Decimal('-3.456')
>>>
Conversion from DecimalTuple is accurate: >>> dt1 = DecimalTuple(sign=1, digits=(0,0,3, 4, 5, 6,7,8,0,0), exponent=-4) ; dt1 ; Decimal(dt1)
DecimalTuple(sign=1, digits=(0, 0, 3, 4, 5, 6, 7, 8, 0, 0), exponent=-4)
Decimal('-3456.7800') # Leading zeroes are dropped. Trailing zeroes are kept.
>>>
Conversion from well formed list or tuple is accurate: >>> t1=(1,(2,3,4,5,6),-4);
>>> t2=(1,list(t1[1]),-4);
>>> L1=list(t1);
>>> L2=list(t2);
>>> t1;t2;L1;L2;
(1, (2, 3, 4, 5, 6), -4)
(1, [2, 3, 4, 5, 6], -4)
[1, (2, 3, 4, 5, 6), -4]
[1, [2, 3, 4, 5, 6], -4]
>>> [ Decimal(v) for v in (t1,t2,L1,L2) ]
[Decimal('-2.3456'), Decimal('-2.3456'), Decimal('-2.3456'), Decimal('-2.3456')]
>>>
>>> { Decimal(v) for v in (t1,t2,L1,L2) }
{Decimal('-2.3456')}
>>>
Using Decimal objects[edit | edit source]Decimal objects work well with the usual arithmetic operators: >>> from decimal import *
>>> D = Decimal
>>> D('234.567') + D('.000000000000000456')
Decimal('234.567000000000000456')
>>> D('234.567') - D('.000000000000000456')
Decimal('234.566999999999999544')
>>> D('234.567') * D('.000000000000000456')
Decimal('1.06962552E-13')
>>> D('234.567') / D('.000000000000000456')
Decimal('514401315789473684.2105263158')
>>> D('234.567') ** ( D('1')/D(3) )
Decimal('6.167213327076116022863000610') # Cube root.
>>> D('234.567') % 1
Decimal('0.567') # the fractional part.
>>> D('-234.567') % 1
Decimal('-0.567')
>>> D('-45234.567') % 360
Decimal('-234.567')
>>>
If you are doing much heavy math containing cube roots, it might be advantageous for you to write your own cube root function using Newton's method, for example. Newton's method is much faster than raising a number to the power (1/3).
>>> d1 = D(-5);d1
Decimal('-5')
>>> abs(d1)
Decimal('5')
>>>
>>> Decimal( ascii(123.456) )
Decimal('123.456')
>>>
>>> bool(D(4))
True
>>> bool(D(0))
False
>>>
>>> complex( D('34.56') ) ; complex(4, D(3))
(34.56+0j)
(4+3j)
>>>
>>> divmod ( -D('2345678.0987654321'), 360 )
(Decimal('-6515'), Decimal('-278.0987654321'))
>>> divmod ( -D('2345678.0987654321'), 1 )
(Decimal('-2345678'), Decimal('-0.0987654321'))
>>>
>>> float ( -D('2345678.0987654321'))
-2345678.098765432
>>>
>>> int ( -D('2345678.987654321'))
-2345678
>>>
>>> isinstance( D(10), Decimal )
True
>>> type( D(10) )
<class 'decimal.Decimal'>
>>>
>>> max(100, -23, D(44))
100
>>> min(100, -23, D(44))
-23
>>>
>>> pow(3,D(2))
Decimal('9')
>>>
>>> sorted(( 3,45,-100, D('234.56') ))
[-100, 3, 45, Decimal('234.56')]
>>>
>>> str(D('456.78'))
'456.78'
>>>
>>> sum(( 3,45,-100, D('234.56') ))
Decimal('182.56')
>>>
Decimal objects and attributes[edit | edit source]>>> D('3.14159').as_integer_ratio()
(314159, 100000)
>>> D(3.14159).as_integer_ratio()
(3537115888337719, 1125899906842624)
>>>
>>> D('3.14159').as_tuple()
DecimalTuple(sign=0, digits=(3, 1, 4, 1, 5, 9), exponent=-5)
>>> D(3.14159).as_tuple()
DecimalTuple(sign=0, digits=(3, 1, 4, 1, 5, 8, 9, 9, 9, 9, 9, 9, 9, 9, ............... , 7, 0, 9, 9, 6, 0, 9, 3, 7, 5), exponent=-50)
>>>
>>> D(3.14159).compare(D('3.14159'))
Decimal('-1') # D(3.14159) < D('3.14159')
>>>
>>> D('-3.14159').copy_abs()
Decimal('3.14159')
>>> abs(D('-3.14159'))
Decimal('3.14159')
>>>
>>> D('-3.14159').is_normal()
True
>>> D('-3.14159').is_zero()
False
>>> D('-3.14159').is_infinite()
False
>>>
>>> D(7).max(D(-9))
Decimal('7')
>>> D(7).max_mag(D(-9))
Decimal('-9')
>>> D(7).min(D(-9))
Decimal('-9')
>>> D(7).min_mag(D(-9))
Decimal('7')
>>>
>>> Decimal('1.41421356').quantize(Decimal('1.000'))
Decimal('1.414')
>>> Decimal('1.41451356').quantize(Decimal('1.000'))
Decimal('1.415')
>>> Decimal('1.41421356').quantize(Decimal('.001'))
Decimal('1.414')
>>> Decimal('1.41451356').quantize(Decimal('.001'))
Decimal('1.415')
>>>
>>> Decimal('0.321000e+2').normalize()
Decimal('32.1')
>>> Decimal('3.2100e1').normalize()
Decimal('32.1')
>>> Decimal('32100.00000e-3').normalize()
Decimal('32.1')
>>>
Exponential operations[edit | edit source]>>> D('1').exp()
Decimal('2.718281828459045235360287471') # Value of e, base of natural logarithms.
>>> D('2').exp()
Decimal('7.389056098930650227230427461') # e ** 2
>>> D('3.14159').ln() # Natural logarithm (base e).
Decimal('1.144729041185178381216412580') # e ** 1.144729... = 3.14159
>>> (D('1234.5678').ln()).exp()
Decimal('1234.567800000000000000000000') # This simple test gives an impressive result.
>>> (D('1234.5678').exp()).ln()
Decimal('1234.567800000000000000000000') # This also.
>>>
>>> # Raising number to power:
>>> D('1.6') ** D('2.3')
Decimal('2.947650308163391181711649979')
>>> D('1.6').ln()*D('2.3').exp()
Decimal('4.687901952522058518151002058')
>>> (D('1.6').ln()*D('2.3')).exp() # Parentheses are important.
Decimal('2.947650308163391181711649980')
>>>
>>> D('1.6').sqrt() # Method .sqrt() is very fast.
Decimal('1.264911064067351732799557418')
>>>
Logical operations[edit | edit source]The Decimal object >>> D(1100020011).logical_and(D(1111))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.InvalidOperation: [<class 'decimal.InvalidOperation'>]
>>>
When using python's decimal module for logical operations on integers, convert int to appropriate Decimal object first. >>> int1 = 23 ; bin(int1)
'0b10111'
>>> D( bin(int1)[2:] )
Decimal('10111')
>>>
After the logical operation convert Decimal object with appearance of binary number to int. >>> int(str(D('10111')),2)
23
>>>
>>> D(110001111).logical_invert()
Decimal('1111_1111_1111_1111_1110_0111_0000') # Default precision of 28.
>>> # Same as:
>>> D(110001111).logical_xor(D('1'*getcontext().prec))
Decimal('1111_1111_1111_1111_1110_0111_0000')
>>>
>>> D(110001111).logical_and(D(1111))
Decimal('1111')
>>> # Equivalent to:
>>> bin(int('18F',16) & int('1111',2))
'0b1111'
>>>
>>> D(1_1000_1111).logical_xor(D('1100_1100'))
Decimal('1_0100_0011')
>>>
>>> D(110001111).shift(3) # +ve means shift left.
Decimal('110001111000')
>>> D(110001111).shift(-3) # -ve means shift right.
Decimal('110001') # Bits shifted out to the right are lost.
>>> getcontext().prec=10
>>> D(110001111).shift(3)
Decimal('1111000') # Bits shifted out to the left are lost.
>>>
>>> getcontext().prec=10
>>> D(110001111).rotate(3) # To left.
Decimal('1111011')
>>> D(110001111).rotate(-3) # To right.
Decimal('1110110001')
>>> # Same as:
>>> D('0110001111').logical_and(D(111)).shift(7) + D('0110001111').shift(-3)
Decimal('1110110001')
>>>
Context objects and attributes[edit | edit source]Contexts are environments for arithmetic operations. They govern precision, set rules for rounding, determine which signals are treated as exceptions, and limit the range for exponents. After the decimal module has been imported, three supplied contexts are available: >>> from decimal import *
>>>
>>> DefaultContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> BasicContext
Context(prec=9, rounding=ROUND_HALF_UP, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[Clamped, InvalidOperation, DivisionByZero, Overflow, Underflow])
>>>
>>> ExtendedContext
Context(prec=9, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[])
>>>
After the decimal module has been imported, the current context is the same as DefaultContext. >>> from decimal import *
>>>
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> str(getcontext())
'Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])'
>>>
>>> str(getcontext()) == str(DefaultContext)
True
>>>
After importing the decimal module, set the current context (if necessary) as appropriate for your planned use of the decimal module. >>> from decimal import *
>>> getcontext().prec = 20
>>> getcontext().clear_flags()
>>> getcontext()
Context(prec=20, rounding=ROUND_HALF_UP, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[Clamped, InvalidOperation, Overflow, Underflow])
>>>
For a list of valid signals: >>> getcontext().flags['']=False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'valid values for signals are:\n [InvalidOperation, FloatOperation, DivisionByZero,\n Overflow, Underflow, Subnormal, Inexact, Rounded,\n Clamped]'
>>>
To create a new context copy an existing context: >>> myContext = BasicContext # This creates a shallow copy.
>>>
>>> myContext = BasicContext.copy()
>>> myContext.prec = 88
>>> myContext ; BasicContext
Context(prec=88, rounding=ROUND_HALF_UP, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[Clamped, InvalidOperation, DivisionByZero, Overflow, Underflow]) # Deep copy.
Context(prec=9, rounding=ROUND_HALF_UP, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[Clamped, InvalidOperation, DivisionByZero, Overflow, Underflow])
>>>
or use the constructor Context(): >>> from decimal import *
>>>
>>> myContext = Context() ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> str(myContext) == str(DefaultContext)
True
>>> myContext = Context(rounding=ROUND_HALF_UP,flags=[]) ; myContext
Context(prec=28, rounding=ROUND_HALF_UP, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> myContext = Context(Emax=9999, flags=[], traps=[InvalidOperation, DivisionByZero]) ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=9999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero])
>>>
To modify a context: >>> myContext.Emin = -9999 ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-9999, Emax=9999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero])
>>>
>>> myContext.flags[Inexact] = True ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-9999, Emax=9999, capitals=1, clamp=0, flags=[Inexact], traps=[InvalidOperation, DivisionByZero])
>>>
>>> myContext.traps = DefaultContext.traps ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-9999, Emax=9999, capitals=1, clamp=0, flags=[Inexact], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> myContext.clear_flags() ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-9999, Emax=9999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>>
>>> myContext.clear_traps() ; myContext
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-9999, Emax=9999, capitals=1, clamp=0, flags=[], traps=[])
>>>
To set current context: >>> from decimal import *
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[InvalidOperation, DivisionByZero, Overflow])
>>> setcontext(Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[], traps=[]))
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[], traps=[])
>>>
The rules are:
>>> getcontext().clear_flags() ; getcontext().clear_traps() ; getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[])
>>>
>>> 1/0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[])
>>>
>>> Decimal(1)/0
Decimal('Infinity')
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[DivisionByZero], traps=[])
>>>
>>> getcontext().clear_flags() ; getcontext().clear_traps()
>>>
>>> 1234567890123456789012345678901234567890+2
1234567890123456789012345678901234567892
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[], traps=[])
>>>
>>> 1234567890123456789012345678901234567890+Decimal(2)
Decimal('1.234567890123456789012345679E+39')
>>> getcontext()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999, Emax=999999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>>>
Methods[edit | edit source]Many of the methods available for Context objects have the same names as corresponding methods available for Decimal objects, for example : max, quantize, shift and sqrt. However they usually take an extra argument so that they make sense when attached to Context object; >>> Decimal(3).sqrt()
Decimal('1.7320508075688772935274463415058723669428053')
>>> BasicContext.sqrt(Decimal(3))
Decimal('1.73205081')
>>>
Others such as clear_traps(), clear_flags() make sense only when attached to Context object. Context objects are useful if you want to perform an arithmetic operation in a temporary environment without changing current environment. >>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>>> Context(prec=99).ln(Decimal(5))
Decimal('1.60943791243410037460075933322618763952560135426851772191264789147417898770765776463013387809317961')
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>>>
.......
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[], traps=[])
>>> Context(prec=14).create_decimal_from_float(1.13 - 1.1)
Decimal('0.030000000000000')
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[], traps=[])
>>>
Also if you want to apply a trap to a conversion without affecting current environment: >>> getcontext().clear_flags()
>>> Decimal('123.456').quantize(Decimal('.01'))
Decimal('123.46')
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>>> getcontext().clear_flags()
>>> Context(traps=[Inexact]).quantize( Decimal('123.456'), Decimal('.01') )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
decimal.Inexact: [<class 'decimal.Inexact'>]
>>> getcontext()
Context(prec=44, rounding=ROUND_HALF_UP, Emin=-999, Emax=999, capitals=1, clamp=0, flags=[], traps=[])
>>>
To check the result of arithmetic operation in a temporary environment: >>> myContext=Context(prec=14, flags=[], traps=[], rounding=ROUND_HALF_UP, Emax=99, Emin=-99)
>>> myContext
Context(prec=14, rounding=ROUND_HALF_UP, Emin=-99, Emax=99, capitals=1, clamp=0, flags=[], traps=[])
>>> myContext.quantize( Decimal('123.456'), Decimal('.01') )
Decimal('123.46')
>>> myContext
Context(prec=14, rounding=ROUND_HALF_UP, Emin=-99, Emax=99, capitals=1, clamp=0, flags=[Inexact, Rounded], traps=[])
>>>
>>> myContext.flags[Inexact]
True
>>> myContext.flags[Overflow]
False
>>>
Is Decimal object int? >>> d1 = Decimal('123.000');d1
Decimal('123.000')
>>> myContext.clear_flags()
>>> myContext.quantize( d1, Decimal(1) )
Decimal('123')
>>> myContext.flags[Inexact]
False # Conversion was exact. d1 is equivalent to int.
>>>
>>> d1 = Decimal('123.007');d1
Decimal('123.007')
>>> myContext.clear_flags()
>>> myContext.quantize( d1, Decimal(1) )
Decimal('123')
>>> myContext.flags[Inexact]
True # Conversion was not exact. d1 is not equivalent to int.
>>>
Making the Decimal object[edit | edit source]The following function verifies that we are working with Decimal objects. It provides for a tuple containing two decimal objects to simulate a complex number. from decimal import *
getcontext().prec += 5 # Extra digits for intermediate steps.
D = Decimal
def makeDecimal (x) :
'''
x is a single object convertible to Decimal object/s.
returns Decimal object or
tuple containing 2 Decimal objects to simulate complex or
exits with error status
'''
if isinstance(x, Decimal) :
return (x)
if isinstance(x, int) or isinstance(x, float) :
return (Decimal(str(x)))
if isinstance(x, complex) :
return ( Decimal(str(x.real)), Decimal(str(x.imag)) )
if isinstance(x, str) :
status = 0
try : x1 = Decimal(x)
except : status = -1
if not status : return x1
status = 0
try : x1 = eval(x)
except : status = -1
if status :
print ('makeDecimal: bad status after eval.')
exit(79)
if isinstance(x1, str) :
print ('makeDecimal: eval must not produce another string.')
exit(78)
return makeDecimal(x1)
print ('makeDecimal: input must be Decimal, int, float, complex or str.')
exit(77)
|
Trigonometric Functions
[edit | edit source]
The following trigonometric functions are sufficient to convert from complex type polar to complex type rectangular and vice-versa. For the functions and see Recipes. arctan x[edit | edit source]We'll try first because it's easily understood and implemented, and it can be used to calculate because the result of this expression (as in all theoretical math) is in radians.
def arctan (tanθ) : # The Python interpreter recognizes international text. Handy.
# value returned is in radians.
# Check input:
x = makeDecimal (tanθ)
if not isinstance(x, Decimal) :
print ('arctan: type of input should be Decimal.')
exit(79)
if not x : return 0 # tan(0) = 0
if abs(x) > 1 :
# abs() function is valid for Decimal objects.
print ('arctan: abs(x) should be <= 1.')
exit(78)
if x < 0 :
print ('arctan: input must be >= 0.')
exit(77) # Only non-negative values in this implementation.
sum = x
x_sq = x*x
divisor = 3
last_dividend = x
multiplier = -1
getcontext().prec += 2 # extra digits for intermediate steps
almostZero = Decimal('1e-' + str(getcontext().prec))
while 1 :
this_dividend = last_dividend * x_sq
if this_dividend < almostZero : break
sum += multiplier * this_dividend / divisor
last_dividend = this_dividend
multiplier *= -1
divisor += 2
getcontext().prec -= 2 # Restore original precision.
return sum+0 # Apply original precision to sum.
Calculating π[edit | edit source]There are many ways to calculate . For example:
Based on the following, we will perform six calculations of and compare the results.
precision = getcontext().prec
getcontext().prec = 502
tan6 = (
( (D('10') - D('20').sqrt()).sqrt() + D('3').sqrt() - D('15').sqrt() )
/
2
)
tan7_5 = D(6).sqrt() - D(3).sqrt() + D(2).sqrt() - 2
tan15 = D(2) - D(3).sqrt()
tan22_5 = D(2).sqrt() - 1
tan30 = D('3').sqrt()/3
tan36 = ( D(5) - 2*(D(5).sqrt()) ).sqrt()
values = [
(tan6, 30),
(tan7_5, 24),
(tan15, 12),
(tan22_5, 8),
(tan30, 6),
(tan36, 5)
]
L2 = [ (v[1]*arctan(v[0])).quantize(
D('1e-'+str(getcontext().prec-3)), rounding=ROUND_HALF_UP
)
for v in values
]
print ('number of calculations =', len(L2))
set2 = set(L2)
len(set2) == 1 or exit(93) # All values in L2 must be equal.
π, = list(set2)
str_π = str(π)
L3 = [
str_π[start:start+70]
for start in range(0,len(str_π),70)
]
str1 = '"' + '" + \n"'.join(L3) + '"'
print (
'''
π = (
{} )
len(str(π)) = {}
isinstance(π, Decimal): {}
'''.format ( str1,
len(str_π),
isinstance(π, Decimal)
)
)
getcontext().prec = precision
number of calculations = 6
π = (
"3.14159265358979323846264338327950288419716939937510582097494459230781" +
"6406286208998628034825342117067982148086513282306647093844609550582231" +
"7253594081284811174502841027019385211055596446229489549303819644288109" +
"7566593344612847564823378678316527120190914564856692346034861045432664" +
"8213393607260249141273724587006606315588174881520920962829254091715364" +
"3678925903600113305305488204665213841469519415116094330572703657595919" +
"5309218611738193261179310511854807446237996274956735188575272489122793" +
"81830119491" )
len(str(π)) = 501
isinstance(π, Decimal): True
All six calculations agree to 500 digits of precision and π is available globally as a Decimal object. tan (Θ / 2)[edit | edit source]def tanθ_2 (tanθ):
'''
tan(θ/2) = csc θ - cot θ
x = tanθ
'''
x = makeDecimal(tanθ)
if not isinstance(x, Decimal) :
print ('tanθ_2: x should be Decimal.')
exit(76)
if x == 0 : return 0
if x < 0 :
# Only non-negative values in this implementation.
print ('tanθ_2: input < 0.')
exit(75)
getcontext().prec += 2 # extra digits for intermediate steps
cscθ = ((Decimal(1)+x*x).sqrt()) / x
cotθ = Decimal(1)/x
tan_θ2 = cscθ - cotθ
getcontext().prec -=2
return (tan_θ2 + 0)
decATAN2 (y, x)[edit | edit source]For the corresponding function using floating point arithmetic see math.atan2(y, x) This function invokes . If and the result is However this function invokes with a value less than so that the expression in the expansion of vanishes as rapidly as possible. Therefore:
def decATAN2 (y,x) :
'''
y
Input is value -.
x
Both x,y must be convertible to Decimal object.
Only 1 of x or y may be 0.
Returns value of angle in degrees.
Value of π must be available globally.
'''
x = makeDecimal(x)
if not isinstance(x, Decimal) :
print ('decATAN2: type of x',type(x),'should be Decimal.')
exit(70)
y = makeDecimal(y)
if not isinstance(y, Decimal) :
print ('decATAN2: type of y',type(y),'should be Decimal.')
exit(69)
if x == y == 0 :
print ('decATAN2: both x and y cannot be 0.')
exit(68)
if y == 0 :
if x > 0 : return 0
return 180
if x == 0 :
if y > 0 : return 90
return 270
if abs(x) == abs(y) :
if x > 0 :
if y > 0 : return 45
return 360-45
if y > 0 : return 180-45
return 180+45
getcontext().prec += 2 # Extra digits for intermediate steps.
tanθ = abs(y)/abs(x)
flip = 0
if tanθ > Decimal('3.078') : # > 72 degrees
tanθ = 1/tanθ
flip += 1
reductionCount = 0
while tanθ > Decimal('0.325') : # > 18 degrees
tanθ = tanθ_2 (tanθ)
reductionCount += 1
θ = arctan(tanθ)
if flip :
θ = π/2 - θ
else :
while reductionCount :
θ *= 2
reductionCount -= 1
θinDegrees = θ*180/π
if x > 0 :
if y < 0 :
θinDegrees = 360-θinDegrees
else :
if y > 0 :
θinDegrees = 180-θinDegrees
else :
θinDegrees = 180+θinDegrees
getcontext().prec -= 2
return θinDegrees+0
degreesToRadians (θinDegrees)[edit | edit source]def degreesToRadians (θinDegrees) :
'''
Value of π must be available globally.
Value returned: -π < radians <= π
-180 degrees is returned as π.
270 degrees is returned as -π/2
'''
x = makeDecimal(θinDegrees)
if not isinstance(x, Decimal) :
print ('degreesToRadians: type of x should be Decimal.')
exit(54)
x = x % 360
if x < 0 : x += 360
if x > 180 : x -= 360
return x * π/180
|
Complex Functions
[edit | edit source]
Within the context of this page a complex number is contained in a tuple with three members, thus: (modulus, phaseInDegrees, 'polar')
The above tuple represents complex number . Or: (realPart, imaginaryPart, 'rect')
where are the rectangular coordinates of complex number . The four values are all Decimal objects. The rectangular format is useful for addition and subtraction of complex numbers. The polar format is useful for raising a complex number to a power including a power less than unity. Both formats are useful for multiplication, division and square root. When working with polar format it's generally more advantageous to work with a positive modulus. Therefore:
and, for example:
and the other sqrt of is: . >>> (2j)**2 ; (-2j)**2
(-4+0j)
(-4+0j)
>>>
Both of the following complex tuples equate to 0: >>> (0,0,'rect')
>>> (0,anyNumber,'polar')
The following functions will enable us to do some serious math with complex numbers, such as solving the cubic equation with three real roots. Administrative functions[edit | edit source]checkComplex(x)[edit | edit source]This function verifies that the object is a valid complex tuple. def checkComplex(x) :
if not isinstance(x,tuple) :
print ('checkComplex: input type should be tuple')
exit(69)
if not (len(x) == 3) :
print ('checkComplex:', 'len(x) =', len(x), 'should be 3 in', x)
exit(68)
if not isinstance(x[0], Decimal) :
print ('checkComplex:', 'x[0] =', x[0], 'should be Decimal in', x)
exit(67)
if not isinstance(x[1], Decimal) :
print ('checkComplex:', 'x[1] =', x[1], 'should be Decimal in', x)
exit(66)
if not isinstance (x[2], str) :
print ('checkComplex: x[2] must be str.')
exit(65)
if (x[2] not in ('polar', 'rect')) :
print ('checkComplex:', 'x[2] =', x[2], 'should be "polar"/"rect" in', x)
exit(64)
makeComplex(x)[edit | edit source]def makeComplex(x) :
'''
Input can be tuple with 1,2 or 3 members.
If 1 or 2 members, 'rect' is understood.
The one member or single object may be int, float, complex, CompleX
or string convertible to int, float or complex.
x = makeComplex(4)
x = makeComplex((4,))
x = makeComplex(('4',0))
x = makeComplex((4,'0', 'rect'))
x = makeComplex(4+0j)
x = makeComplex('4+0j')
x = makeComplex(('4+0j',))
In all seven cases above x = ( Decimal('4'), Decimal('0'), 'rect' )
output is always
(modulus, phase, "polar") or
(real_part, imag_part, "rect")
modulus, phase, real_part, imag_part are Decimal objects.
'''
if isinstance(x, CompleX) : # New class CompleX (note the spelling.)
x.check()
return (x.r, x.i, 'rect')
if isinstance(x, tuple) :
if len(x) not in (1,2,3) :
print ('makeComplex: input has wrong length.',len(x),'should be 3.')
exit(76)
if len(x) == 3 :
if not isinstance (x[2], str) :
print ('makeComplex: x[2] must be str.')
exit(75)
if x[2] not in ('polar','rect') :
print ('makeComplex: type of complex in x[2] not recognized.')
exit(75)
v1,v2,type_ = makeComplex(x[:2])
output = (v1,v2,x[2])
checkComplex(output)
return output
if len(x) == 2 :
v1,v2 = [ makeDecimal (v) for v in x ]
if not isinstance(v1, Decimal) :
print ('makeComplex: v1 should be Decimal.')
exit(74)
if not isinstance(v2, Decimal) :
print ('makeComplex: v2 should be Decimal.')
exit(73)
output = (v1,v2, 'rect')
return output
x = x[0]
t1 = makeDecimal (x)
if isinstance(t1,Decimal) :
output = (t1,Decimal(0),'rect')
return output
if (isinstance(t1, tuple) and len(t1) == 2) :
output = t1 + ('rect',)
checkComplex(output)
return output
print ('makeComplex: t1 must be tuple with length 2.')
exit(72)
convertComplex(x)[edit | edit source]def convertComplex(x) :
'''
If input is rectangular, output is polar and vice-versa
'''
x = makeComplex(x)
if x[2] == 'polar' :
modulus, phase = x[0], x[1]
θinRadians = degreesToRadians (phase)
cosθ = cos(θinRadians)
sinθ = sin(θinRadians)
a,b = (modulus*cosθ, modulus*sinθ)
output = (a,b,'rect')
else :
real,imag = x[0], x[1]
modulus = (real*real + imag*imag).sqrt()
phase = decATAN2(imag, real)
output = (modulus, phase, 'polar')
output = makeComplex(output)
return output
clean_complex (x)[edit | edit source]def clean_complex (x) :
'''
output = clean_complex (x)
output is input returned as complex tuple
with values "cleaned".
1e-50 is returned as 0.
12.999999999999999..........9999999999999999999999
is returned as 13.
12.888888888888........8888888888888888888888888888 is left unchanged.
Note the following Decimal operations:
>>> getcontext().prec = 20
>>> Decimal('3.9999999999999999999999999999999')
Decimal('3.9999999999999999999999999999999')
>>> Decimal('3.9999999999999999999999999999999')+0
Decimal('4.0000000000000000000')
>>> (Decimal('3.9999999999999999999999999999999')+0).normalize()
Decimal('4')
>>>
>>> Decimal(76500)
Decimal('76500')
>>> Decimal(76500).normalize()
Decimal('7.65E+4')
>>> Decimal(76500).normalize()+0
Decimal('76500')
>>>
Hence the line: ((value+0).normalize()+0)
'''
x = makeComplex(x)
getcontext().prec -= 3
almostZero = Decimal ('1e-' + str(getcontext().prec) )
L1 = [
( ( (v, Decimal(0))[int(offset)] + 0 ).normalize() + 0 )
for v in x[:2]
for offset in (
(v > -almostZero) and (v < almostZero),
)
]
for offset in range (0,2,1) :
v1 = L1[offset]
if v1 == 0 : pass
else :
t1 = v1.as_tuple()
if len( t1[1] ) < getcontext().prec : pass
else : L1[offset] = x[offset]
getcontext().prec += 3
while x[2] == 'polar' :
if L1[0] == 0 :
L1[1] = L1[0] ; break
if L1[0] < 0 :
L1[0] = (L1[0]).copy_negate()
L1[1] += 180
L1[1] %= 360
if L1[1] <= -180 : L1[1] += 360
if L1[1] > 180 : L1[1] -= 360
break
return tuple(L1)
CompleX_to_complex (x)[edit | edit source]def CompleX_to_complex (x) :
'''
complex, float = CompleX_to_complex (x)
'''
x = makeComplex(x)
if x[2] == 'polar' : x = convertComplex (x)
x = clean_complex (x)
cx1 = complex( float(x[0]), float(x[1]) )
fl1 = float(cx1.real)
if cx1.imag : fl1 = None
return (cx1, fl1)
Arithmetic functions[edit | edit source]addComplex(v1,v2)[edit | edit source]def addComplex(v1,v2) :
'''
SuM = addComplex(v1,v2)
Calculation is rectangular.
The spelling of SuM indicates type CompleX.
'''
ToP, BottoM = [ CompleX(v) for v in (v1,v2) ]
sum = (ToP.r+BottoM.r, ToP.i+BottoM.i, 'rect')
SuM = CompleX(sum)
return SuM
subtractComplex(v1,v2)[edit | edit source]def subtractComplex(v1,v2) :
'''
DifferencE = subtractComplex(v1,v2)
where DifferencE = v1 - v2
Calculation is rectangular.
'''
ToP, BottoM = [ CompleX(v) for v in (v1,v2) ]
difference = (ToP.r-BottoM.r, ToP.i-BottoM.i, 'rect')
DifferencE = CompleX(difference)
return DifferencE
multiplyComplex (v1, v2)[edit | edit source]def multiplyComplex (v1, v2) :
'''
ProducT = multiplyComplex (multiplicand, multiplier)
Calculation is 'rect'.
'''
ToP, BottoM = [ CompleX(v) for v in (v1,v2) ]
a,b = ToP.r, ToP.i
c,d = BottoM.r, BottoM.i
product = ( a*c-b*d, b*c+a*d, 'rect' )
ProducT = CompleX(product)
return ProducT
divideComplex (dividend, divisor)[edit | edit source]def divideComplex (dividend, divisor) :
'''
QuotienT = divideComplex (dividend, divisor)
Calculation is 'polar'.
divisor must be non-zero.
if dividend == 0, output is 0.
'''
DividenD, DivisoR = [ CompleX(v) for v in (dividend, divisor) ]
if not DivisoR.m :
print ('divideComplex: polar divisor must be non-zero.')
exit(88)
if (not DividenD.m) : return CompleX(0)
quotient = (DividenD.m/DivisoR.m, DividenD.p-DivisoR.p, 'polar')
return CompleX(quotient)
Exponential functions[edit | edit source]complexSQRT (x)[edit | edit source]def complexSQRT (x) :
'''
RooT = complexSQRT (x)
calculation is 'polar'.
'''
CX1 = CompleX(x)
if not CX1.m : return CompleX(0)
modulus, phase = CX1.m, CX1.p
if modulus < 0 :
modulus *= -1 ; phase += 180
modulus = modulus.sqrt()
phase = phase/2
root = (modulus,phase,'polar')
return CompleX(root)
complexCUBEroot (x)[edit | edit source]def complexCUBEroot (x) :
'''
RooT = complexCUBEroot (x)
'polar' output is useful because the other 2 cube roots are:
(root[0], root[1]+120, 'polar')
(root[0], root[1]+240, 'polar')
'''
CX1 = CompleX(x)
if not CX1.m : return CompleX(0)
# Calculating the cube root is a polar operation.
modulus, phase = CX1.m, CX1.p
if modulus < 0 :
modulus *= -1 ; phase += 180
modulus_of_root = modulus ** ( Decimal(1) / Decimal(3) )
phase_of_root = phase/3
root = (modulus_of_root, phase_of_root, 'polar')
RooT = CompleX(root)
return RooT
|
class CompleX
[edit | edit source]
class CompleX :
'''
This class has 5 attributes:
self.r : the real coordinate of the complex number
self.i : the imaginary coordinate of the complex number
self.m : the modulus of the complex number
self.p : the phase of the complex number in degrees
self.c : the class expressed as Python type complex
'''
def __init__(self, value=0):
self.set(value)
return
def check(self) :
# print ('entering check(self) :')
precisions = [] ; status = 0
for v in (self.r, self.i, self.m, self.p) :
if not isinstance(v, Decimal) :
print ('value not Decimal:', str(v)[:20]) ; status = -1 ; continue
precisions += [len(v.as_tuple()[1])]
if not isinstance(self.c, complex) :
print ('self.c not complex:', str(self.c)[:20]) ; status = -1
if status : exit(39)
cx1, not_used = CompleX_to_complex ((self.r, self.i, 'rect'))
if cx1 != self.c : print ("""
for self = {}
Rect values don't match self.c:
{}
{}""".format(
self,cx1,self.c
)
)
cx1, not_used = CompleX_to_complex ((self.m, self.p, 'polar'))
if cx1 != self.c : print ("""
for self = {}
Polar values don't match self.c:
{}
{}""".format(
self,cx1,self.c
)
)
return
def set(self, value=Decimal(0)):
# print ('entering set(self, ',value,'):')
t1 = makeComplex(value)
if t1[2] == 'rect' :
self.r, self.i = t1[:2]
if not self.r and not self.i : self.m = self.p = Decimal(0)
else :
t1 = convertComplex(value)
self.m, self.p = t1[:2]
else :
self.m, self.p = t1[:2]
if not self.m : self.r = self.i = self.p = Decimal(0)
else :
t1 = convertComplex(value)
self.r, self.i = t1[:2]
self.c, not_used = CompleX_to_complex ((self.r, self.i, 'rect'))
self.check()
return
def clean(self) :
# print ('entering clean(self) :')
self.check()
#output = clean_complex (x)
self.r, self.i = clean_complex ((self.r, self.i, 'rect'))
self.m, self.p = clean_complex ((self.m, self.p, 'polar'))
self.check()
return
def _print(self) :
'''
output a string containing all the info
about self and suitable for printing.'''
self.check()
return '''
self = {}
real = {}
imag = {}
modulus = {}
phase = {}
as type complex: {}
'''.format(
self,
self.r, self.i,
self.m, self.p,
self.c,
)
The above code highlights the power of Python in a new class called CompleX. When a new instance of this class is created, it can be accessed through its attributes and changed through its methods with the simplest of syntax. Seemingly complicated functions such as complex cube root can be implemented in only a few lines of simple code. Examples[edit | edit source]CX1 = CompleX()
print ('isinstance(CX1, CompleX):', isinstance(CX1, CompleX))
print ('CX1 =', CX1._print())
isinstance(CX1, CompleX): True
CX1 =
self = <__main__.CompleX object at 0x101a463c8>
real = 0
imag = 0
modulus = 0
phase = 0
as type complex: 0j
CX2 = CompleX((5,30,'polar'))
print ('isinstance(CX2, CompleX):', isinstance(CX2, CompleX))
print ('CX2 =', CX2._print())
isinstance(CX2, CompleX): True
CX2 =
self = <__main__.CompleX object at 0x101245240>
real = 4.33012701892219323381861585376468
imag = 2.50000000000000000000000000000000
modulus = 5
phase = 30
as type complex: (4.330127018922194+2.5j)
CX3 = CompleX('-5+12j')
print ('isinstance(CX3, CompleX):', isinstance(CX3, CompleX))
print ('CX3 =', CX3._print())
isinstance(CX3, CompleX): True
CX3 =
self = <__main__.CompleX object at 0x101416f28>
real = -5.0
imag = 12.0
modulus = 13.0
phase = 112.619864948040426172949010876680
as type complex: (-5+12j)
CX2 = CX1 # shallow copy.
CX2 = CompleX(CX1) # deep copy.
CX3.check() # This should not produce error.
CX3.set(-7.5)
print ('CX3 =', CX3._print())
self = <__main__.CompleX object at 0x101415f60>
real = -7.5
imag = 0
modulus = 7.5
phase = 180
as type complex: (-7.5+0j)
|
Solving the cubic equation
[edit | edit source]
The cubic equation is expressed thus: where both are non-zero. We will solve the equation as an example of the use of Python's Decimal module and the new class CompleX. See Figure 1. a,b,c,d = (2,1,-16,-15)
A = 2*b*b*b - 9*a*b*c + 27*a*a*d
C = b*b-3*a*c
p = -3*C
q = -A
The depressed cubic and Vieta's substitution[edit | edit source]Let , substitute this value of in the cubic equation and produce the depressed cubic Let , substitute this value of in the depressed equation and produce the quadratic: where . Therefore, . disc = q*q - 4*C*C*C
RooT = complexSQRT (disc)
DividenD = addComplex ( -q, RooT )
W = divideComplex(DividenD, 2)
isinstance(W, CompleX) or exit(48)
r = Decimal(3).sqrt()
W.clean()
print ('\n################\nW =', W._print())
################
W =
self = <__main__.CompleX object at 0x10137cbe0>
real = -665
imag = 685.892119797275408236868751236328
modulus = 955.33920677422215800938250724701
phase = 134.113967095785813976410473653471
as type complex: (-665+685.8921197972754j)
The cube roots[edit | edit source]RooT1 = complexCUBEroot(W)
t1 = (RooT1.m, RooT1.p+120, 'polar')
t2 = (RooT1.m, RooT1.p-120, 'polar')
RooT2 = CompleX(t1)
RooT3 = CompleX(t2)
for root in ( RooT1, RooT2, RooT3 ) :
root.clean()
print ('++++++++++++++++\nRooTx:', root._print())
print ('''
self.i
------- = {}
sqrt(3)
'''.format(
float(root.i/r)
)
)
# Each value of w has format (k + 1j*m*sqrt(3)). It can be shown that t = 2k.
t = 2*root.c.real
x = -(b+t)/(3*a)
result = a*x*x*x + b*x*x + c*x + d
print ('x =', x, 'result =',result)
++++++++++++++++
RooTx:
self = <__main__.CompleX object at 0x1012572e8>
real = 7
imag = 6.92820323027550917410978536602346
modulus = 9.84885780179610472174621141491761
phase = 44.7046556985952713254701578844903
as type complex: (7+6.928203230275509j)
self.i
------- = 4.0
sqrt(3)
x = -2.5 result = 0.0
++++++++++++++++
RooTx:
self = <__main__.CompleX object at 0x101257240>
real = -9.5
imag = 2.59807621135331594029116951225890
modulus = 9.84885780179610472174621141491761
phase = 164.704655698595271325470157884490
as type complex: (-9.5+2.598076211353316j)
self.i
------- = 1.5
sqrt(3)
x = 3.0 result = 0.0
++++++++++++++++
RooTx:
self = <__main__.CompleX object at 0x10136d400>
real = 2.5
imag = -9.52627944162882511440095487828231
modulus = 9.84885780179610472174621141491761
phase = -75.2953443014047286745298421155097
as type complex: (2.5-9.526279441628825j)
self.i
------- = -5.5
sqrt(3)
x = -1.0 result = 0.0
The three roots of the given cubic are . |
Assignments
[edit | edit source]
When calculating :
>>> Decimal(str(math.pi))
Decimal('3.141592653589793') # π accurate to precision 16.
>>>
>>> Decimal(math.pi)
Decimal('3.141592653589793115997963468544185161590576171875') # Not an accurate value of π. Why?
>>> # 3.14159265358979323846264338327950288419716939937510582097494459230781 # Correct value of π.
What are the square roots of ?
'polar' 'polar'. 'polar' = 'polar'
What is the number ? What are the other two cube roots of ? . The other two cube roots of are: 'polar'
|
Further Reading or Review
[edit | edit source]
|
References
[edit | edit source]Python's built-in functions:
Python's documentation:
Decimal fixed point and floating point arithmetic, A first look at classes