Browse DevX
Sign up for e-mail newsletters from DevX


A Developer's Guide to Python 3.0: Numbers, Strings, and Data : Page 3

Python 3.0 makes critical—and not-backwardly-compatible—changes to data types. Find out how these changes will affect your code.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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.

Thanks for your registration, follow us on our social networks to keep up-to-date