Python Concepts/Using Python's Decimal module

Objective

edit
 
  • Learn about Python's Decimal module.
  • Learn about Decimal objects.
  • Review examples of code containing Decimal objects.
  • Learn how to use Decimal objects to perform calculations on complex numbers.
  • Become familiar with numbers containing many thousands of decimal digits.
  • Create a new class CompleX.
  • Use class CompleX and Python's Decimal module to solve the cubic equation.

Lesson

edit

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

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

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).


Decimal objects work well with many of the familiar built-in functions:

>>> 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
>>> 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
>>> 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

The Decimal object D('1110010100011') has the appearance of a binary number and logical operations act on Decimal objects whose only digits are 0,1.

>>> 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

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:

  • A bad operation sets a flag.
  • A good operation does not clear a flag.
  • Before a decimal operation set traps and clear flags.
  • After a decimal operation process exceptions and check flags.
>>> 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

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

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

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

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

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

  .

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

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
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

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

checkComplex(x)

edit

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
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
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
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
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

addComplex(v1,v2)

edit
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
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
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
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

complexSQRT (x)

edit
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
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
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
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
 
Figure 1: Cubic function and X intercepts.

Origin at point  .
Intercepts at points  

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

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
 
Figure 2: Three cube roots of W.
  = RooT1;   = RooT2;   = RooT3.
 .
Phase of  
Phase of  
Phase of  
Phase of  
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
 
  • Calculate the value of   to 10,000 places of decimals.
 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 π.
  •   =   'polar' .
 What are the square roots of  ?
   'polar'  'polar' .   'polar'  =   'polar' 
  •   'polar'  is one of the cube roots of a certain number  .
 What is the number  ?
 What are the other two cube roots of  ?
  
 .
 The other two cube roots of   are:   'polar' 
  • Given that CX1 is type CompleX, add method sqrt() to class CompleX so that the expression CX1.sqrt() returns the square root of CX1 as type CompleX.

Further Reading or Review

edit

References

edit

Python's built-in functions:

"abs()",

Python's documentation:

Decimal fixed point and floating point arithmetic, A first look at classes