### WEBINAR:

On-Demand

Application Security Testing: An Integral Part of DevOps

### Floating Point Improvements

The rejected PEP-754 attempted to make Python fully IEEE 754 compliant. It was rejected because Python (the CPython implementation) relies on the underlying C library to handle IEEE 754 special values such as

NaN (not a number), and positive and negative infinity. There are many inconsistencies across different platforms. Still, Python 3.0 (and 2.6 too), incorporated many floating point improvements, and both implement the IEEE 754 standard much more closely.

The

float() function that turns strings into floating point numbers now understands

nan,

+inf (or

inf), and

-inf and turns them into the Not A Number, Positive and Negative Infinity IEEE 754 values. (Case doesn't matter, so

NaN,

INF, etc., are valid too.)

The math module now has the functions

isnan() and

isinf(). The

isinf() function doesn't distinguish between

inf,

+inf and

-inf. Here are some examples:

```
>>> float('nan')
nan
>>> float('NaN') # Any case works
nan
>>> float('+inf')
inf
>>> float('-inf')
-inf
>>> float('INF')
inf
>>> float('nan') + float('inf')
nan
>>> float('inf') + float('-inf')
nan
>>> float('inf') - float('-inf')
inf
>>> import math
>>> math.isnan(float('nan'))
True
>>> math.isinf(float('inf'))
True
>>> math.isinf(float('-inf'))
True
>>> math.isinf(float('nan'))
False
>>> math.isnan(float('-inf'))
False
```

The math module has now also a

copysign(x, y) function that returns the absolute value of

x with the sign of

y. I don't understand why this function exists instead of a simple

sign() function that returns

-1,

1 or a couple of

ispositive(),

isnegative() functions. The documentation is very succinct:

```
>>> help(math.copysign)
Help on built-in function copysign in module math:
copysign(...)
copysign(x,y)
Return x with the sign of y.
```

However,

copysign works as advertised except for

NaN. If you try to copy the sign of

NaN you get inconsistent results—a negative sign on Mac OS X and a positive sign on Windows. A

closed bug says this behavior is OK. I disagree.

NaN is not a number, and as such has no sign. Trying to copy the sign of

NaN is like trying to copy the sign of any other non-number value (string, list, object) and should result in an exception.

Some other functions related to floating point numbers were added to the math module too.

math.fsum() adds up the stream of numbers from an iterable, and is careful to avoid loss of precision by using partial sums (unlike the built-in

sum() function). If any of the numbers are

NaN, the result is

NaN. If the partial sum reaches

+inf or

-inf, the

sum() function returns that as the result. The

math.fsum() function raises an OverflowError exception, which is more in the spirit of IEEE 754:

```
>>> sum([1e308, 1, -1e308])
0.0
>>> math.fsum([1e308, 1, -1e308])
1.0
>>> sum([1e100, 1, -1e100, -1])
-1.0
>>> math.fsum([1e100, 1, -1e100, -1])
0.0
>>> x = [1e308, 1e308, -1e308]
>>> sum(x)
inf
>>> math.fsum(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: intermediate overflow in fsum
>>> sum([float('nan'), 3.3])
nan
>>> math.fsum([float('nan'), -float('nan')])
nan
```

The functions

acosh(),

asinh(), and

atanh() compute inverse hyperbolic functions. The

log1p() function returns the natural logarithm of

1+x (base e). The

trunc() function rounds a number toward zero, returning the closest integer value:

```
>>> math.acosh(30)
4.0940666686320855
>>> math.acosh(1)
0.0
>>> math.asinh(1)
0.88137358701954305
>>> math.asinh(0)
0.0
>>> math.atanh(0.5)
0.54930614433405489
>>> math.log1p(2)
1.0986122886681098
>>> math.trunc(-1.1)
-1
>>> math.trunc(-1.9)
-1
>>> math.trunc(1.1)
1
>>> math.trunc(1.9)
1
>>> math.trunc(3.0)
3
```

You can convert floating-point numbers to or from hexadecimal strings. The conversion functions convert floats to and from a string representation without introducing rounding errors from the conversion between decimal and binary (if there are enough digits to represent the number fully). Floats have a

hex() method that returns a string representation, while the

float.fromhex() method converts a string back into a number (as accurately as possible):

```
>>> x = 4.2
>>> a.hex()
'0x1.0cccccccccccdp+2'
>>> float.fromhex('0x1.0cccccccccccdp+2')
4.2000000000000002
```

The decimal module was updated to version 1.66 of the General Decimal Specification. New features include some methods for some basic mathematical functions such as

exp() and

log10():

```
>>> Decimal(1).exp()
Decimal("2.718281828459045235360287471")
>>> Decimal("2.7182818").ln()
Decimal("0.9999999895305022877376682436")
>>> Decimal(1000).log10()
Decimal("3")
```

The

as_tuple() method of Decimal objects now returns a named tuple (more on named tuples in future article) with sign, digits, and exponent fields:

```
>>> Decimal('-3.3').as_tuple()
DecimalTuple(sign=1, digits=(3, 3), exponent=-1)
```

A new variable in the sys module,

float_info, is an object that contains information derived from the

float.h file about the platform's floating-point support:

```
>>> sys.float_info
sys.floatinfo(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308,
min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15,
mant_dig=53, epsilon=2.2204460492503131e-16, radix=2, rounds=1)
```

Overall, Python has definitely elevated its level of support for floating point numbers—but it is not perfect yet. The

Numpy external package is still the best tool for serious number crunching in Python.