Decimal

for Beginners

PyCon 2006

Facundo Batista

facundo@taniquetil.com.ar

www.taniquetil.com.ar

Graphic art: Diana Batista

Some structure and content has been taken from Michael Chermside's presentation

Index

Why you might care?

Technical background

Let's use it!

Advanced use

Why You Might Care?

What Python always had

A simple calculation

What Python Always Had

Doesn't Python already have decimals?

>>> x = 5.2

>>> x + 0.3

5.5

>>> x + 0.4

5.6000000000000005

This is Binary Floating Point! Yes, so?

Simple Calculation

Charge a 5% (to the nearest cent) on a $0.70 bill.

By hand: 0.70

x 5

-----

3.5 which rounds to 4 cents

Binary floating point:

>>> 0.7 * 0.05

0.034999999999999996 which rounds to 3 cents

Decimal:

>>> Decimal("0.7") * Decimal("0.05")

Decimal("0.035") which rounds to 4 cents

Simple Calculation

So, in short:

By hand: 4 cents

Binary floating point: 3 cents

Decimal: 4 cents

On a million bills... do you want to lose $10,000?

Motivated? Let's go!

Technical Background

Defining Floating Point

Representation error

Choices for the designer

About standards

And what about Java?

Defining Floating Point

How old is Python?

5.10 * 108 seconds

Structure:

+/- mmmm * bb +/- ee

Vocabulary:

Mantissa: mmmm (precision, # of digits)

Radix: bb

Exponent: ee

Sign: +/-

Normally we write it...

+/- mmm E +/-ee

E usually means bb=10

Representation Error

It happens to us everyday:

1/4 = 0.2500 and (1/4)*4 = 0.2500*4 = 1

1/3 = 0.3333 and (1/3)*3 = 0.3333*3 = 0.9999

Why? Because we can not use infinite quantity of 3's.

This also happens in floating point

Because the mantissa has not infinite digits

Be it Binary or Decimal

Big difference

You expect it to happen in Decimal

But not in Binary:

>>> 11 / 10.0

1.1000000000000001

Choices for the Designer

Basic stuff

Radix, precision, allowed exponents

Decimal means the radix is 10

Lots of fancy details:

Normalizing, negative zero, infinities, NaNs, rounding policies, trailing zeroes, etc, etc, etc...

How we made that choices?

We didn't, :p

Standards!

About Standards

IEEE Standards 754, 854

They are very complex

Hard to read and follow

And your brain explodes!

General Decimal Arithmetic Spec, by Cowlishaw

Easier to read and implement

Your brain doesn't explode (just melts...)

Cowlishaw also provided test cases!

And What About Java?

Java

Binary floating point in 1995 (Java 1.0)

Decimal fixed point (BigDecimal) in 1997 (Java 1.1)

Decimal floating point in 2004 (Java 1.5), based on Cowlishaw spec.

Python

Binary floating point in 1991 (Python 0.9)

Decimal floating point in 2004 (Python 2.4), based on Cowlishaw spec.

Let's Use It!

First steps

Some arithmetic

Conversions

Comparisons

Mixing types

First Steps

Don't forget to import the package!

>>> import decimal

You can create instances from ints (or longs)...

>>> d = decimal.Decimal(25)

>>> d

Decimal("25")

...or from strings

>>> decimal.Decimal('4.37')

Decimal("4.37")

>>> decimal.Decimal('3.8e-5')

Decimal("0.000038")

Some Arithmetic

No much to learn here...

>>> import decimal

>>> d = decimal.Decimal('2.3')

>>> d

Decimal("2.3")

>>> x = d + d

>>> x

Decimal("4.6")

>>> x - d

Decimal("2.3")

>>> x * 34

Decimal("156.4")

Some Arithmetic

Other operations:

>>> x # ...from before

Decimal("4.6")

>>> y = x / 3

>>> y

Decimal("1.533333333333333333333333333")

>>> y ** 10

Decimal("71.83990835200693661196633289")

>>> y.sqrt()

Decimal("1.238278374733780664732379179")

Conversions

Converting to strings:

>>> import decimal

>>> str( decimal.Decimal('10.3') )

'10.3'

Exponents are used if not all digits are significant:

>>> ten = decimal.Decimal(10)

>>> x = decimal.Decimal(348) * ten ** -9

>>> str(x)

'3.48E-7'

>>> x.to_eng_string()

'348E-9'

Conversions

Truncating to integer is easy:

>>> import decimal

>>> x = decimal.Decimal('32.5254')

>>> x

Decimal("32.5254")

>>> int(x)

32

But rounding has a strange syntax:

need to pass an example to quantize()

it'll copy the exponent, adjusting the mantissa

>>> x.quantize(decimal.Decimal('1'))

Decimal("33")

>>> x.quantize(decimal.Decimal('1.01'))

Decimal("32.53")

Comparisons

Comparisons are simple:

>>> import decimal

>>> x = decimal.Decimal('2.55')

>>> y = decimal.Decimal('89.0002')

>>> x == y

False

>>> x > y

False

>>> x < y < 100

True

>>> max(x, y)

Decimal("89.0002")

Mixing Types

With int or long, they convert to Decimal:

>>> import decimal

>>> decimal.Decimal("3.4") + 5

Decimal("8.4")

You can not mix it with strings!

>>> decimal.Decimal("3.4") + "2"

Traceback (most recent call last):

...

TypeError: You can interact Decimal only with int, long or Decimal data types.

Mixing with floats (or complex) also fails.

Advanced Use

Context objects

Using the context

Rounding

Signals, Traps & Flags

Context objects

Decimal objects have a per-thread context object

Controls several things, including:

# of digits of precision (the mantissa max length)

rounding rules

whether to throw exceptions or raise flags

access to flags that were raised

>>> decimal.getcontext()

Context(prec=28, rounding=ROUND_HALF_EVEN, ..., flags=[], traps=[DivisionByZero,

InvalidOperation, Overflow])

decimal.getcontext() returns the context object

of the current thread

just modify it!

Using the Context

>>> import decimal

>>> c = decimal.getcontext()

>>> c.prec

28

>>> decimal.Decimal(1) / 7

Decimal("0.1428571428571428571428571429")

>>> c.prec = 5

>>> decimal.Decimal(1) / 7

Decimal("0.14286")

>>> c.rounding

'ROUND_HALF_EVEN'

>>> c.rounding = decimal.ROUND_FLOOR

>>> decimal.Decimal(1) / 7

Decimal("0.14285")

Rounding

When rounding happens?

if the mantissa is not big enough in an operation

if you force it with quantize()

The mantissa does not have enough digits:

>>> import decimal

>>> m = decimal.Decimal(2) / 3

>>> m

Decimal("0.6666666666666666666666666667")

Force it with quantize():

>>> cents = decimal.Decimal('0.01')

>>> m.quantize(cents)

Decimal("0.67")

Signals, Traps & Flags

At certain circumstances, a condition is signaled

overflow, the number got rounded, etc..

If the trap is active you get an Exception

>>> c = decimal.getcontext()

>>> c

Context(..., traps=[DivisionByZero, ...])

>>> decimal.Decimal(1) / 0

Traceback (most recent call last): (...)

decimal.DivisionByZero: x / 0

If not, you only get a flag set

>>> c.traps[decimal.DivisionByZero] = False

>>> decimal.Decimal(1) / 0

Decimal("Infinity")

>>> getcontext()

Context(..., flags=[DivisionByZero], ...)

Questions?

Suggestions?

Thank you!

Facundo Batista

facundo@taniquetil.com.ar

www.taniquetil.com.ar

Licencia: Creative Commons
Atribución-NoComercial-CompartirDerivadasIgual 2.5 Argentina
http://creativecommons.org/licenses/by-nc-sa/2.5/deed.es_AR

Decimal Structure: Finite numbers, Special values

Decimal Methods: as_tuple, quantize, to_eng_strings, to_integral, adjusted, same_quantum, normalize, compare, max and min, sqrt

Using Context: Structure of a Context, Getting the current context, Setting a new context, Standard contexts, DefaultContext

Context Methods: Standard arithmetic, Specific I, Specific II

Signals: What are Signals?, Dealing with Flags, Dealing with Traps, Clamped and DivisionByZero, Inexact and Rounded, InvalidOperation and Overflow, Subnormal and Underflow

Rounding: When rounding happens, Up & down, Ceiling & floor, Half up, down & even

Usage Cases: Do use Decimal: Financial, Do use Decimal: Easier life, Don't use Decimal: Speed / Accuracy, Don't use Decimal: Directly as Money

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Needed if want to extend Decimal to trigonometry.

Facundo Batista

2005-03-06T16:13:33