Created
February 26, 2016 07:58
-
-
Save carlozamagni/187e478f516cac926682 to your computer and use it in GitHub Desktop.
Python support for IEEE 754 double-precision floating-point numbers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# double.py | |
# Copyright (C) 2006, 2007 Martin Jansche | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining | |
# a copy of this software and associated documentation files (the | |
# "Software"), to deal in the Software without restriction, including | |
# without limitation the rights to use, copy, modify, merge, publish, | |
# distribute, distribute with modifications, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be | |
# included in all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
# IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | |
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | |
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR | |
# THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
# | |
# Except as contained in this notice, the name(s) of the above copyright | |
# holders shall not be used in advertising or otherwise to promote the | |
# sale, use or other dealings in this Software without prior written | |
# authorization. | |
""" | |
Support for IEEE 754 double-precision floating-point numbers. The | |
purpose is to work around the woefully inadequate built-in | |
floating-point support in Python. Functionality is a blend of the | |
static members of java.lang.Double and bits of <float.h> and <math.h> | |
from C99. | |
""" | |
from __future__ import division | |
from math import fabs as _fabs | |
import struct as _struct | |
def doubleToRawLongBits(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: long | |
@return: the IEEE 754 bit representation (64 bits as a long integer) | |
of the given double-precision floating-point value. | |
""" | |
# pack double into 64 bits, then unpack as long int | |
return _struct.unpack('Q', _struct.pack('d', value))[0] | |
def longBitsToDouble(bits): | |
""" | |
@type bits: long | |
@param bits: the bit pattern in IEEE 754 layout | |
@rtype: float | |
@return: the double-precision floating-point value corresponding | |
to the given bit pattern C{bits}. | |
""" | |
return _struct.unpack('d', _struct.pack('Q', bits))[0] | |
NAN = longBitsToDouble(0x7ff8000000000000L) | |
POSITIVE_INFINITY = longBitsToDouble(0x7ff0000000000000L) | |
MAX_VALUE = longBitsToDouble(0x7fefFfffFfffFfffL) | |
MIN_NORMAL = longBitsToDouble(0x0010000000000000L) | |
MIN_VALUE = longBitsToDouble(0x0000000000000001L) | |
NEGATIVE_ZERO = longBitsToDouble(0x8000000000000000L) | |
NEGATIVE_INFINITY = longBitsToDouble(0xFff0000000000000L) | |
# same as DBL_EPSILON in <float.h> | |
EPSILON = longBitsToDouble(doubleToRawLongBits(1.0) + 1) - 1.0 | |
def fpclassify(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: str | |
@return: a string indicating the classification of the given value as | |
one of 'NAN', 'INFINITE', 'ZERO', 'SUBNORMAL', or 'NORMAL'. | |
""" | |
bits = doubleToRawLongBits(value) | |
exponent = (bits >> 52) & 0x7ff | |
if exponent == 0x7ff: | |
# INFINITE or NAN | |
mantissa = bits & 0x000fFfffFfffFfffL | |
if mantissa == 0: | |
return 'INFINITE' | |
else: | |
return 'NAN' | |
elif exponent == 0: | |
# ZERO or SUBNORMAL | |
mantissa = bits & 0x000fFfffFfffFfffL | |
if mantissa == 0: | |
return 'ZERO' | |
else: | |
return 'SUBNORMAL' | |
else: | |
assert exponent > 0 and exponent < 0x7ff | |
return 'NORMAL' | |
def isnan(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: bool | |
@return: C{True} if given value is not a number; | |
C{False} if it is a number. | |
""" | |
return value != value | |
def isinf(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: bool | |
@return: C{True} if the given value represents positive or negative | |
infinity; C{False} otherwise. | |
""" | |
return value == POSITIVE_INFINITY or value == NEGATIVE_INFINITY | |
def isfinite(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: bool | |
@return: C{True} if the given value is a finite number; | |
C{False} if it is NaN or infinity. | |
""" | |
return (value == value | |
and value != POSITIVE_INFINITY | |
and value != NEGATIVE_INFINITY) | |
def isnormal(value): | |
""" | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: bool | |
@return: C{True} if the given value is a normal floating-point number; | |
C{False} if it is NaN, infinity, or a denormalized | |
(subnormal) number. | |
""" | |
if value != value: | |
return False | |
value = abs(value) | |
return value >= MIN_NORMAL and value <= MAX_VALUE | |
def doubleToLongBits(value): | |
""" | |
Similar to L{doubleToRawLongBits}, but standardize NaNs. | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: long | |
@return: the IEEE 754 bit representation (64 bits) of the given | |
floating-point value if it is a number, or the bit | |
representation of L{NAN} if it is not a number. | |
""" | |
if value != value: | |
# value is NaN, standardize to canonical non-signaling NaN | |
return 0x7ff8000000000000L # NaN bit pattern | |
else: | |
return doubleToRawLongBits(value) | |
def signbit(value): | |
""" | |
Test whether the sign bit of the given floating-point value is | |
set. If it is set, this generally means the given value is | |
negative. However, this is not the same as comparing the value | |
to C{0.0}. For example: | |
>>> NEGATIVE_ZERO < 0.0 | |
False | |
since negative zero is numerically equal to positive zero. But | |
the sign bit of negative zero is indeed set: | |
>>> signbit(NEGATIVE_ZERO) | |
True | |
>>> signbit(0.0) | |
False | |
@type value: float | |
@param value: a Python (double-precision) float value | |
@rtype: bool | |
@return: C{True} if the sign bit of C{value} is set; | |
C{False} if it is not set. | |
""" | |
return (doubleToRawLongBits(value) >> 63) == 1 | |
def copysign(x, y): | |
""" | |
Return a floating-point number whose absolute value matches C{x} | |
and whose sign matches C{y}. This can be used to copy the sign of | |
negative zero, as follows: | |
>>> copysign(1, NEGATIVE_ZERO) | |
-1.0 | |
@type x: float | |
@param x: the floating-point number whose absolute value is to be copied | |
@type y: number | |
@param y: the number whose sign is to be copied | |
@rtype: float | |
@return: a floating-point number whose absolute value matches C{x} | |
and whose sign matches C{y}. | |
@postcondition: (isnan(result) and isnan(x)) or abs(result) == abs(x) | |
@postcondition: signbit(result) == signbit(y) | |
""" | |
if y < 0 or (y == 0 and signbit(y)): | |
result = - _fabs(x) | |
else: | |
result = _fabs(x) | |
assert (isnan(result) and isnan(x)) or _fabs(result) == _fabs(x) | |
assert signbit(result) == signbit(y) | |
return result | |
def fdiv(x, y): | |
""" | |
Divide two numbers according to IEEE 754 floating-point semantics. | |
Division by zero does not raise an exception, but produces | |
negative or positive infinity or NaN as a result. | |
>>> fdiv(+1, +0.0) == POSITIVE_INFINITY | |
True | |
>>> fdiv(-1, +0.0) == NEGATIVE_INFINITY | |
True | |
>>> fdiv(+1, -0.0) == NEGATIVE_INFINITY | |
True | |
>>> fdiv(-1, -0.0) == POSITIVE_INFINITY | |
True | |
>>> isnan(fdiv(0.0, 0.0)) | |
True | |
@type x: number | |
@param x: the dividend | |
@type y: number | |
@param y: the divisor | |
@rtype: float | |
@return: the quotient C{x/y} with division carried out according | |
to IEEE 754 semantics. | |
""" | |
if x != x or y != y: | |
return NAN | |
elif y == 0: | |
# treat y==0 specially to avoid raising a ZeroDivisionError | |
if x == 0: | |
# 0/0 | |
return NAN | |
elif (x < 0) == signbit(y): | |
# signs cancel, result is positive | |
return POSITIVE_INFINITY | |
else: | |
# opposite signs, result is negative | |
return NEGATIVE_INFINITY | |
elif x == 0: | |
# this case is treated specially to handle e.g. fdiv(0, 1<<1024) | |
if signbit(x) == (y < 0): | |
# signs cancel, result is positive | |
return 0.0 | |
else: | |
# opposite signs, result is negative | |
#return -0.0 | |
#^^^^^^^^^^^ this doesn't work in Python 2.5 due to a bug | |
return NEGATIVE_ZERO | |
else: | |
try: | |
# NB: __future__.division MUST be in effect. Otherwise | |
# integer division will be performed when x and y are both | |
# integers. Unfortunately the current (Python 2.4, 2.5) | |
# behavior of __future__.division is weird: 1/(1<<1024) | |
# (both arguments are integers) gives the expected result | |
# of pow(2,-1024), but 1.0/(1<<1024) (mixed integer/float | |
# types) results in an overflow error. The surrounding | |
# try/except block attempts to work around this issue. | |
return x / y | |
except OverflowError: | |
# only necessary to handle big longs: scale them down | |
# until they fit into a double | |
assert x == x and x != 0 | |
assert y == y and y != 0 | |
negative = (x < 0) == (y > 0) | |
x = abs(x) | |
y = abs(y) | |
n = 0 | |
s = 1 | |
while n < 1024: | |
n += 1 | |
s <<= 1 | |
x,q = divmod(x, s) | |
y,r = divmod(y, s) | |
#print 'n=%d s=%d x=%g q=%g y=%g r=%g' % (n, s, x, q, y, r) | |
try: | |
result = (x+q/s) / (y+r/s) | |
except OverflowError: | |
continue | |
if negative: | |
return -result | |
else: | |
return result | |
# scaling didn't work, so attempt to carry out division | |
# again, which will result in an exception | |
return x / y | |
if __name__ == '__main__': | |
# run some basic sanity checks | |
assert doubleToRawLongBits(1.0) == 0x3ff0000000000000L | |
NONSTD_NAN = longBitsToDouble(0x7ff8000000000001L) | |
assert isnan(NONSTD_NAN) | |
assert doubleToLongBits(NONSTD_NAN) == doubleToLongBits(NAN) | |
assert isnan(POSITIVE_INFINITY + NEGATIVE_INFINITY) | |
assert isnan(0 * POSITIVE_INFINITY) | |
assert isnan(0 * NEGATIVE_INFINITY) | |
BIG = 1 << 1024 | |
for x in (NAN, BIG, MAX_VALUE, 1.0, 0.0, | |
-NAN, -BIG, -MAX_VALUE, -1.0, NEGATIVE_ZERO): | |
assert isnan(fdiv(x, NAN)) | |
assert isnan(fdiv(NAN, x)) | |
assert fdiv(BIG, 0) == POSITIVE_INFINITY | |
assert fdiv(-BIG, 0) == NEGATIVE_INFINITY | |
assert fdiv(0, BIG) == 0 | |
assert fdiv(0, -BIG) == 0 | |
assert signbit(fdiv(0, -BIG)) | |
assert fdiv(NEGATIVE_ZERO, BIG) == 0 | |
assert signbit(fdiv(NEGATIVE_ZERO, BIG)) | |
assert fdiv(-1e-200, +1e200) == NEGATIVE_ZERO | |
assert fdiv(+1e-200, -1e200) == NEGATIVE_ZERO | |
assert fdiv(-1e-200, +(1<<1024)) == NEGATIVE_ZERO | |
assert fdiv(+1e-200, -(1<<1024)) == NEGATIVE_ZERO | |
assert fdiv(+BIG, 0.5) == POSITIVE_INFINITY | |
assert fdiv(-BIG, 0.5) == NEGATIVE_INFINITY | |
assert copysign(0, -1) == NEGATIVE_ZERO | |
import doctest | |
doctest.testmod() | |
# eof |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment