floating point numbers in python floats in python are platform dependent, but usually equivalent to...

13
Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand is also finite, we start having issues with precision significantly earlier…. As a practical matter, the smallest float is usually on the order of ~ 2.225 x 10 -308 (-1) sign ·(1.b -1 ·b -2 ·b -3 ...b - 52 )·2 (ex-1023)

Upload: rosamund-wade

Post on 25-Dec-2015

212 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

Floating point numbers in PythonFloats in Python are platform dependent, but usually

equivalent to an IEEE754 64-bit C “double”

However, because the significand is also finite, we start having issues with precision significantly earlier….

As a practical matter, the smallest float is usually on the order of ~ 2.225 x 10-308

(-1)sign ·(1.b-1·b-2·b-3...b-52)·2(ex-1023)

Page 2: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

A numerically stable numeric class

The idea is to define a class, log_float that we can use as if its objects were regular python floats, even though stored values

are represented internally in log space, and all of the arithmetic operations that we will need (multiplication, division and

addition) are also defined in such a way that they also take place in log space. We should be able to write:

We should be able to freely mix log_floats, floats, and ints

Developing the log_float numeric class

A = log_float(2)

B = log_float(3)

C = 2.1 * (A + B)

print C #result is 10.2

A = log_float(2)

C = A * 2

print C # result is 4

or…

Page 3: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

Special Method Attributes Handling zero values in log space is a nuisance

We’ll use the built-in None object to represent LOGZERO

We’ll adopt the convention of internally representing values as “extended logarithms” where zero valued reals are handled as a special case.

if value: ## for positive reals

self.value = math.log(value)

else: ## for value was zero

self.value = LOGZERO

Page 4: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

Special Method Attributes These are methods used internally by python that are usually not directly invoked. They are called automatically when needed

We will use SMAs to simulate a numeric class

def __init__(self):

pass

The class constructor method __init__ is an example of a special method attribute you are already familiar with

def __str__(self):

return “This is how my object should print”

The class method __str__ allows you to specify how an instance should print itself when the print funciton is invoked

Page 5: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

Special Method Attributes These are the special methods we will minimally need to

define for our numerically stable log_float class:

Some hints as to how each must behave…

__init__(self, value, mode = None):

__str__(self):

__mul__(self,other):

__rmul__(self,other):

__add__(self,other):

__div__(self,other):

__rdiv__(self,other):

Page 6: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

“log_float” Special Method Attributes __init__(self, value, mode = None)

Unless mode = True, this function should accept as its first argument a floating point real-space number provided by the user. This number will be then log transformed and stored in an instance variable self.value.

In the special case where value is zero, self.value should be set to LOGZERO (or None)

The only time that the mode flag will be set is when the constructor has been called not by the user, but instead by an arithmetic operation involving another log_float object. If this is the case, the value being passed in is already in log space, so the self.value variable should just be set to value.

We should also make sure that user-specified reals are positive

Page 7: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__str__(self):

This might print a message indicating something about both the internal state of the object and the way that the value would be publically viewed..

So for instance if self.value is None, it might return “Public value is 0, internal value is None”

If self.value is defined, it should return a message indicating that self.value itself is the internal value, and the exponent to e of self.value as the public value.

Natural log values can converted back to reals with math.exp()

“log_float” Special Method Attributes

Page 8: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__mul__(self,other):

Multiplication is easy if both self and other are log_float objects. Since self.value and other.value should both be in log space, you can just return the sum of these values.

But you’ll also need to handle the special cases where one or the other object (or both) has a value variable set to None!!!

“log_float” Special Method Attributes

self and other refer to the operands on the left hand and right hand side of the multiplication operator. These can in principle be of any type

In all cases you should return an object of type log_float so that the result of the operation is, itself, a log_float. And the values you are returning are already in log space, so you should make sure to set the

mode flag of the initializer. i.e.

return log_float(self.value + other.value, True)

Page 9: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__mul__(self,other):

This is easiest to handle with a try/except block. If you try to refer to or evaluate the other.value attribute and it turns out not to exist it will throw an AttributeError:

try:

if self.value is None or other.value is None:

## do some stuff

except AttributeError:

# do something to handle the fact that “other“

# isn’t a log_float. Just assume it’s a float or

# int, then log transform it and carry on

“log_float” Special Method Attributes

What about the special case where the other object isn’t a log_float?

Page 10: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__add__(self,other):

ln(x+y)

“log_float” Special Method Attributes

Log-space addition is a minor PITA. We adopt the procedure suggested by Tobias Mann

x and y have now been forced into a form compatible with our prior log transform. (order matters, as stability demands we keep the exponential term small)

= ln(x) + ln(X+Y) – ln (x)

= ln(x) + ln(x+y) x = ln(x) + ln(1+y)x

= ln(x) + ln(1 + eln(y/x))

= ln(x) + ln(1 + eln(y)-ln(x))

Page 11: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__add__(self,other):

“log_float” Special Method Attributes

Log-space addition is a minor PITA. We adopt the procedure suggested by Tobias Mann

Be careful when making comparisons to None. For instance, try evaluating: -999 < None

Testing for None should be “if X is None”, never “if X == None”

Again, we need to consider several “edge cases”:

• self.value or other.value is LOGZERO

• other is a float or int type so evaluating other.value throws an attribute error

• A combination of the above scenarios

Page 12: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__div__(self,other):

“log_float” Special Method Attributes

You should also define a __truediv__ method that simply returns a call to your __div__ method. __truediv__ is invoked instead of __div__ if you

have used from __future__ import division

Division has similar edge cases as most of the other operations, but in addition we need to handle attempts to

divide by zero. For instance, this would occur if other.value = LOGZERO. We can raise our own

exception to handle this:

if other.value is LOGZERO:

raise ZeroDivisionError

Page 13: Floating point numbers in Python Floats in Python are platform dependent, but usually equivalent to an IEEE754 64-bit C “double” However, because the significand

__cmp__(self,other):

“log_float” Special Method Attributes

It is critical here to remember that LOGZERO is an identity with None, and will NOT evaluate as less than a negative number. You will need

to be especially careful here with all the edge cases.

__cmp__ is invoked by the comparison operators >, >=, ==, <=, < , or implicitly during operations like sorts.

Generally, it should return -1 if object self is “less” than object other, 0 if object self is “equal” to object other,

and 1 if object self is greater than object other.

Another (preferred actually, but more work) option is to individually define each of the separate “rich comparison operator” methods, such as __lt__, __eq__, __gt__ etc.