Skip to content

Instantly share code, notes, and snippets.

@robclewley
Created April 7, 2015 15:37
Show Gist options
  • Save robclewley/8a4f66bc0b9d8665856d to your computer and use it in GitHub Desktop.
Save robclewley/8a4f66bc0b9d8665856d to your computer and use it in GitHub Desktop.
Simulation of any-precision binary IEEE754 numbers (including > 64 bit)

A Python module to simulate binary floating point representation to IEEE 754 standards of arbitrary fixed precision, or to infinite precision

I have written a module to simulate the machine representation of binary floating point numbers and their arithmetic. Values can be of arbitrary fixed precision or infinite precision, along the same lines as the decimal class. The design is loosely based on the decimal module, although it doesn't get in to threads, for instance. You can play with different IEEE 754 representations with different precisions and rounding modes, and compare with infinite precision Binary numbers. For instance, it is easy to learn about machine epsilon, representation/rounding error using a much simpler format such as a 4-bit exponent and 6-bit mantissa. Such a format is easily defined in the new module and can be manipulated easily.

This module is intended to help teach numerical analysis and machine arithmetic.

Documentation and basic examples can be found in the docstring of simfloat.py

"""
Simulation of binary floating point representation at arbitrary fixed or
infinite precision (including greater than 64 bit).
:name: Simfloat
:author: Robert Clewley
:version: 0.1
:contact: rob <dot> clewley <at> gmail <dot> com
:date: August 2008
:license: New BSD
***** Features
* Primarily intended for teaching purposes, e.g. in a numerical analysis course.
Demonstrates representation formats and facilitates exploration of the
distribution of represented values along the real number line.
* Not intended to be the most efficient implementation, but transparency of
implementation helpful for learning about issues in IEEE representation, e.g.
denormalized numbers.
* Open source code, released under the BSD license. Please improve this code
for clarity, bug-fixes and feature improvements, but only to improve
efficiency where it is not at the expense of a reader's easy comprehension of
the algorithms and binary representation.
* Can represent any (sign, characteristic, significand) IEEE 754-style format,
and is not restricted to representations with total precision less than 64
bits. The internal representations and arithmetic are done using arbitrary
precision and do not depend on the python 'float' (64 bit) class.
* Rounding modes offered are 'up', 'down', 'floor', 'ceiling', 'half_up',
and 'half_down'.
* Arithmetic operations are only permitted between numbers represented in
exactly the same format.
* A binary integer class is also implemented with its associated integer
arithmetic and shift operations (no logical operations).
* max, min, sqrt, power functions are defined for the appropriate numeric
classes.
See the following for details, and references therein:
http://en.wikipedia.org/wiki/Floating_point
http://en.wikipedia.org/wiki/Rounding
http://en.wikipedia.org/wiki/IEEE_754
http://en.wikipedia.org/wiki/Binary_numeral_system
***** Known issues
* Binary floating point arithmetic not simulated in its native form, but through
internal use of arbitrary precision Decimal floating point numbers. It would
be preferable to have a pure binary arithmetic implementation that
demonstrates shifting of exponents, etc. before addition.
* Does not directly simulate the algorithmic implementations by which
IEEE 754 arithmetic and rounding is performed in CPUs. e.g. rounding up
is *not* achieved by adding 0.5 and then truncating in this code.
* Creation of higher precision floats is slow due to python implementation of
frexp function.
* Boolean operations on binary floating point numbers are not supported at this
time.
* Please note that float(Decimal("Inf")) will not work in Python 2.5 due to a
problem in Python itself. This has been fixed in Python 2.6 and above
(http://bugs.python.org/issue3188).
* ContextClass does not support initialization from numpy float128 values. Needs
additional code to extract byte-by-byte hex representation from the 'data'
attribute of such a value, and conversion into equivalent binary string.
* eval(repr(x)) when x is a ContextClass instance creates a Binary object with
the same representation (fixed precision, rounding) as x, not another
ContextClass instance. However, x == eval(repr(x)).
* decimal context precision value must not be changed by the user to be less
than the precision required by any ContextClass instances (Binary numbers of a
fixed precision), otherwise arithmetic on those numbers may be inaccurate.
In Python 2.5 and above, local context could be established for these
calculations using the with statement (see here for implementation details:
http://docs.python.org/lib/decimal-decimal.html). See binary_py25.py.
* mod, floordiv, divmod methods are not supported.
* Can be slow to evaluate expressions involving high precision values.
* Can hash a binary context (ContextClass instance), but cannot hash
a decimal.context instance.
* Requires numpy to be installed, in order to use numpy.sign, numpy.zeros
and provide compatibility with numpy.float32, numpy.float64, and
numpy.float128 values. The sign function and array use provide better speed
in key parts of the algorithms, but could be easily replaced to make
numpy installation optional.
* Does not support use of minifloats (IEEE-754 style formats with very low
bit lengths for the exponent and characteristic) for integer-only
representations. (This is a common application of such values, according to
http://en.wikipedia.org/wiki/Minifloat.)
***** Usage
** Constructors:
>>> context = define_context(5, 12, ROUND_DOWN)
Equivalent binary float values in a given context:
>>> x = Binary('-0.1111', context) # binary fraction assumed by default
>>> x = Binary(Decimal("-0.9375"), context) # decimal fraction
The following represent the same binary number (in context) but are a way
to directly specify the representation in terms of the underlying context
>>> a = context('1 01110 111000000000') # (sign, characteristic, significand)
>>> a = context('101110111000000000') # (sign, characteristic, significand)
These alternative forms are also valid, for convenience:
>>> a = context(Decimal("-0.9375"))
>>> a = context(Binary('-0.1111'))
If the python float literal -0.9375 is exactly representable in the context,
then this is also equivalent:
>>> a = context(-0.9375)
>>> a = context(numpy.float64(-0.9375))
Otherwise, the resulting representation in a will be to the "nearest"
representable value under the rules of the context's precision and rounding
mode.
The values in a are instances of the context, and are not Binary class
instances.
Note that a context instance cannot be initialized directly using a string
literal for a binary fraction, to avoid ambiguity with the primary use
case, namely with input strings for (sign, characteristic, significand).
The Binary constructor can also represent *arbitrary* precision binary values in
the absence of a given context. After any of the above definitions of x:
>>> x.context
<class 'binary.Float_5_12_D'>
However:
>>> y = Binary('0.110100101010101011110001111100001e5')
>>> y.context is None
True
Note that infinite binary precision is not possible to specify from a
Decimal object, in case the binary representation is non-terminating.
>>> b = Binary(Decimal("0.1"))
ValueError: Cannot create arbitrary precision binary value without a
representation context
** Views:
For a context instance x (not a Binary instance), we slightly break the
tradition that eval(repr(x)) is identically the same type as x. However,
eval(repr(x)) == x and the evaluation leads to a Binary object with the same
context.
>>> x = context(Decimal("-0.9375"))
>>> x # default 'view' is as binary
Binary("-0.1111E0", (5, 12, ROUND_DOWN))
>>> bx = eval(repr(x))
>>> bx == x
True
>>> bx.context
<class 'binary.Float_5_12_D'>
>>> bx.context == x.context # bx really quacks like a duck
True
>>> x.as_binary() # output always in scientific notation with 0 before radix
Binary("-0.1111E0")
>>> x.as_binary() == bx # x.as_binary() keeps the same context
True
>>> x.as_decimal()
Decimal("-0.9375")
>>> print x
1 01110 111000000000
** Comparisons:
Can only compare like representations from a given context.
>>> d = double(1)
>>> q = quadruple(1)
>>> d == q
ValueError: Mismatched precision
If you want to compare the actual values that these objects represent, convert
them to a Binary number first
>>> bd = Binary(double(1))
>>> bq = Binary(quadruple(1))
>>> bd == bq
True
>>> bd == 1.0 # cannot compare with python-native floats
TypeError: Invalid object for comparison
>>> bd > 1
False
>>> bd == Decimal("1")
True
** Conversions:
For high precision floats with long mantissas, convert them accurately to
Decimal type. Note that Decimal(str(f)) will not be accurate for floats f
with mantissas longer than the displayable length of f by the str function.
Thus float(Decimal(str(f))) != f for some f. To avoid this, create the
representation of the float in the appropriate context by
double(f)
which will be a precise representation of f in double precision, where f
can be a python float, numpy.float64. For numpy.float32 use single(f).
Binary values in one context can be converted (coerced) to another thus:
>>> xs = Binary('-1111.001', single)
>>> xd = Binary(xs, double)
or
>>> xd = Binary(double(xs))
** Arithmetic:
Can only perform arithmetic on representations from a given context with
other instances of the same context (including Binary instances with
identical context).
To perform arbitray precision arithmetic with mixed representations,
first convert to Binary type, which does not care about the precision of
the operands: the operation returns a new Binary value of the precise
result, without context if neither operand had context, otherwise with
the highest precision context. In a tie, arithmetic is not possible unless
the rounding is the same.
>>> bd = Binary(double(1.5))
>>> bq = Binary(quadruple(0.1))
>>> c = bd + bq
>>> print c
0.11001100110011001100110011001100110011001100110011001101E1
>>> c.__class__
<class 'binary.Binary'>
>>> c.context # coerced to the higher precision
<class 'binary.Float_15_112_HU'>
Also see test_simfloat.py for many more examples and validations.
"""
import numpy as npy
import math
import decimal
from decimal import Decimal
__all__ = ['Decimal', 'Binary', 'ContextClass', 'BinaryIntClass',
'quadruple', 'double', 'define_context', 'frexp', 'dec_context',
'single', 'half', 'test', 'binstr2dec', 'decfrac2binrep',
'decint2binstr', 'binfracstr2decfrac', 'dec2binstr', 'binvalstr2dec',
'ROUND_UP', 'ROUND_DOWN', 'ROUND_CEILING', 'ROUND_FLOOR',
'ROUND_HALF_UP', 'ROUND_HALF_DOWN',
'BinaryOverflow', 'BinaryUnderflow', 'BinaryOverflow',
'BinaryNegativeValue', 'BinaryRemainderValue', 'BinaryException']
# Rounding
# up = always away from 0
ROUND_UP = 'ROUND_UP'
# down = always towards 0 (truncate)
ROUND_DOWN = 'ROUND_DOWN'
# ceiling = always towards +inf
ROUND_CEILING = 'ROUND_CEILING'
# floor = always towards -inf
ROUND_FLOOR = 'ROUND_FLOOR'
# half up = to nearest, 0.1b (0.5d) rounds away from 0 (standard and default)
ROUND_HALF_UP = 'ROUND_HALF_UP'
# half down = to nearest, 0.1 (0.5d) rounds towards 0
ROUND_HALF_DOWN = 'ROUND_HALF_DOWN'
_round_code = {ROUND_UP: 'U', ROUND_DOWN: 'D', ROUND_CEILING: 'C',
ROUND_FLOOR: 'F', ROUND_HALF_UP: 'HU', ROUND_HALF_DOWN: 'HD'}
dec_context = decimal.getcontext()
dec_context.prec = 128
class BinaryIntClass(object):
"""Abstract class for non-negative binary integer values only, with a fixed
number of digits given by class attribute 'digits'. Set digits in a concrete
subclass. The initialization value string will cause an exception if the
binary value is longer than the class' digits setting.
"""
digits = None
def __init__(self, value):
"""Initialize with a value made up of a string of binary digits.
"""
dec_value = int(value, base=2)
if dec_value < 0:
raise BinaryNegativeValue("Invalid binary value: out of range of %d-digit number"%self.digits)
elif dec_value > self.largest:
raise BinaryOverflow("Invalid binary value: out of range of %d-digit number"%self.digits)
self.bin_value = pad(value, self.digits)
self.dec_value = Decimal(dec_value)
self.tuple_rep = tuple([int(bit) for bit in self.bin_value])
def __repr__(self):
return '%s("%s")' % (self.__class__.__name__, self.bin_value)
def __str__(self):
return self.bin_value
def as_decimal(self):
return self.dec_value
def as_binary(self):
return 'Binary("%s")' % self.bin_value
def as_tuple(self):
return self.tuple_rep
def _op_return_class(self, other):
try:
other_digits = other.digits
except AttributeError:
raise TypeError("Invalid Binary object")
if other_digits > self.digits:
new_class = other.__class__
else:
new_class = self.__class__
return new_class
def max(self, other):
return self.dec_value.max(other)
def min(self, other):
return self.dec_value.min(other)
def __add__(self, other):
new_class = self._op_return_class(other)
result = self.dec_value + other.dec_value
try:
return new_class(decint2binstr(result))
except ValueError:
raise BinaryOverflow(result)
def __sub__(self, other):
new_class = self._op_return_class(other)
result = self.dec_value - other.dec_value
if result < 0:
raise BinaryNegativeValue(result)
return new_class(decint2binstr(result))
def __mul__(self, other):
new_class = self._op_return_class(other)
result = self.dec_value * other.dec_value
try:
return new_class(decint2binstr(result))
except ValueError:
raise BinaryOverflow(result)
def __div__(self, other):
new_class = self._op_return_class(other)
result_dm = divmod(self.dec_value, other.dec_value)
if result_dm[1] != 0:
raise BinaryRemainderValue(result_dm)
return new_class(decint2binstr(result_dm[0]))
def __rshift__(self, y):
result = self.dec_value >> y
return self.__class__(decint2binstr(result))
def __lshift__(self, y):
result = self.dec_value << y
try:
return self.__class__(decint2binstr(result))
except ValueError:
raise BinaryOverflow(result)
def __hash__(self):
"""Hash as per the precise decimal representation.
"""
return hash(self.dec_value)
def next(self):
try:
return self.__class__(decint2binstr(int(self.bin_value,
base=2) + 1))
except BinaryOverflow:
raise BinaryOverflow(int(self.bin_value, base=2) + 1)
def prev(self):
try:
return self.__class__(decint2binstr(int(self.bin_value,
base=2) - 1))
except:
raise BinaryNegativeValue(int(self.bin_value, base=2) - 1)
def __hash__(self):
return hash(repr(self))
def __getitem__(self, i):
return int(self.bin_value[i])
## could provide boolean operator methods but not needed for arithmetic
def __reduce__(self):
return (self.__class__, (repr(self),))
class SignBit(BinaryIntClass):
digits = 1
largest = 1
class BinaryCharacteristic(BinaryIntClass):
"""Abstract class for floating point representation's characteristic,
a.k.a. exponent.
"""
def __init__(self, value):
"""value is a binary string representatino of the exponent (so may be
negative)"""
BinaryIntClass.__init__(self, value)
# interpretation-related attributes
interp_dec_value = self.dec_value - self.bias
if interp_dec_value < self.exp_lowest:
raise BinaryNegativeValue("Invalid binary value: out of range of %d-digit number"%self.digits)
if interp_dec_value > self.exp_largest:
raise BinaryOverflow("Invalid binary value: out of range of %d-digit number"%self.digits)
self.interp_dec_value = interp_dec_value
def interpret(self, denorm=False):
"""Interpret raw binary value as a signed decimal integer.
"""
if denorm:
# bias needs to be adjusted by 1
return self.interp_dec_value + 1
else:
return self.interp_dec_value
class BinarySignificand(BinaryIntClass):
"""Abstract class for floating point representation's significand,
a.k.a. fraction, or mantissa.
"""
def interpret(self):
"""Interpret raw binary value as a decimal fraction.
"""
return binfracstr2decfrac(self.bin_value)
class ContextClass(object):
"""Abstract class for immutable binary floating point number using
(sign, characteristic, significand) representation.
"""
def __init__(self, value):
"""Initialize with decimal value (either of type 'float' or 'Decimal')
or a binary digit string of total length = <this_class>.digits or the
same string with spaces between sign, characteristic, and significand
elements (total length <this_class>.digits+2).
"""
try:
assert self.significandClass.digits is not None
except AssertionError:
raise NotImplementedError("Create concrete sub-type of ContextClass")
# round_dir may be overwritten by init_from_dec
# round_dir = None => no rounding needed
# 'next' => use next towards +inf
# 'prev' => use next towards -inf
round_dir = None
if isinstance(value, (float, npy.float64)):
if not npy.isfinite(value):
val_str = str(value)
# val_str may be '1.#INF' rather than 'inf' on some platforms
val_str = val_str.replace('.', '').replace('#', '').replace('1',
'')
round_dir = self.init_from_dec(Decimal(val_str))
else:
if value < 0:
fval = -value
s = '1'
else:
fval = value
s = '0'
# extract full binary representation of the value
# (which is not accessible from str(value) or repr(value)
valstr = pad(decint2binstr(npy.int64( \
npy.array(fval).view(long))), 64)
e = valstr[1:12]
f = valstr[12:]
denorm = int(e) == 0 and int(f) > 0
if denorm:
bias = 1022
expo = Decimal(2)**Decimal(int(e, base=2) - bias)
frac = binfracstr2decfrac(f)
else:
bias = 1023
expo = Decimal(2)**Decimal(int(e, base=2) - bias)
frac = 1 + binfracstr2decfrac(f)
try:
round_dir = self.init_from_dec((-1)**int(s) * expo * frac)
except:
raise BinaryOverflow("Invalid representation for this class: %s"%value)
elif isinstance(value, npy.float32):
if not npy.isfinite(value):
val_str = str(value)
# val_str may be '1.#INF' rather than 'inf' on some platforms
val_str = val_str.replace('.', '').replace('#', '').replace('1',
'')
round_dir = self.init_from_dec(Decimal(val_str))
else:
if value < 0:
fval = -value
s = '1'
else:
fval = value
s = '0'
# extract full binary representation of the value
# (which is not accessible from str(value) or repr(value)
valstr = pad(decint2binstr(npy.int32( \
npy.array(fval).view(int))), 32)
e = valstr[1:9]
f = valstr[9:]
denorm = int(e) == 0 and int(f) > 0
if denorm:
bias = 126
expo = Decimal(2) ** Decimal(int(e, base=2) - bias)
frac = binfracstr2decfrac(f)
else:
bias = 127
expo = Decimal(2) ** Decimal(int(e, base=2) - bias)
frac = 1 + binfracstr2decfrac(f)
try:
round_dir = self.init_from_dec((-1)**int(s) * expo * frac)
except:
raise BinaryOverflow("Invalid representation for this class: %s"%value)
elif isinstance(value, (int, long, npy.int64, npy.int32)):
try:
round_dir = self.init_from_dec(Decimal(value))
except OverflowError:
raise BinaryOverflow()
elif isinstance(value, Decimal):
try:
round_dir = self.init_from_dec(value)
except OverflowError:
raise BinaryOverflow()
elif isinstance(value, Binary):
try:
round_dir = self.init_from_dec(value.dec)
except OverflowError:
raise BinaryOverflow()
elif isinstance(value, str):
if len(value) == self.digits:
# e.g. "11101001111010000"
s = value[0]
e = value[1:self.characteristicClass.digits+1]
f = value[self.characteristicClass.digits+1:]
elif len(value) == self.digits + 2:
# e.g. "1 1101 001111010000"
try:
s, e, f = value.split(' ')
except:
raise ValueError("Invalid string representation for this class: %s"%value)
else:
raise ValueError("Invalid string representation for this class: %s"%value)
try:
self.init_from_string(s, e, f)
except:
raise ValueError("Invalid string representation for this class: %s"%value)
else:
raise TypeError("Invalid initialization type")
self.tuple_rep = tuple((self.signbit.as_tuple(),
self.characteristic.as_tuple(),
self.significand.as_tuple()))
# treat special cases for 0, Inf, NaN, and denormalized values
if self.characteristic.dec_value == 0:
if self.significand.dec_value == 0:
# zeros
if self.signbit.dec_value == 0:
self.dec_value = Decimal("0")
self.bin_value = "0"
else:
self.dec_value = Decimal("-0")
self.bin_value = "-0"
else:
# denormalized values
self.dec_value = (-1)**self.signbit.dec_value * \
2**(self.characteristic.interpret(denorm=True)) * \
(0 + self.significand.interpret())
# lazy determination of bin_value when it is requested
# (slow calculation)
self.bin_value = ""
elif self.characteristic.dec_value == self.characteristic.largest:
if self.significand.dec_value == 0:
# +/- Inf
if self.signbit.dec_value == 0:
self.dec_value = Decimal("Inf")
self.bin_value = "Inf"
else:
self.dec_value = Decimal("-Inf")
self.bin_value = "-Inf"
else:
# NaN
self.dec_value = Decimal("NaN")
self.bin_value = "NaN"
else:
# normalized values
self.dec_value = (-1)**self.signbit.dec_value * \
2**(self.characteristic.interpret()) * \
(1 + self.significand.interpret())
# lazy determination of bin_value when it is requested
# (slow calculation)
self.bin_value = ""
if round_dir is not None:
new = getattr(self, round_dir)()
self.signbit = new.signbit
self.characteristic = new.characteristic
self.significand = new.significand
self.bin_value = new.bin_value
self.dec_value = new.dec_value
self.tuple_rep = new.tuple_rep
# for compatibility with Binary attribute
self.context = self.__class__
def init_from_string(self, s, char_str, mant_str):
self.signbit = SignBit(s)
self.characteristic = self.characteristicClass(char_str)
self.significand = self.significandClass(mant_str)
def init_from_dec(self, d):
"""d is a Decimal object"""
if d._isnan():
self.signbit = SignBit('0')
self.characteristic = \
self.characteristicClass("1"*self.characteristicClass.digits)
self.significand = \
self.significandClass("1"*self.significandClass.digits)
return None
elif d._isinfinity() != 0:
self.signbit = SignBit(str(bin_sign(d._isinfinity())))
self.characteristic = \
self.characteristicClass("1"*self.characteristicClass.digits)
self.significand = \
self.significandClass("0"*self.significandClass.digits)
return None
if d < 0:
s = '1'
d = -d
elif d == 0:
self.signbit = SignBit('0')
self.characteristic = \
self.characteristicClass("0"*self.characteristicClass.digits)
self.significand = \
self.significandClass("0"*self.significandClass.digits)
# no rounding, so return None
return None
else:
s = '0'
if d <= self.largest_denorm:
bias = self.characteristicClass.bias-1
frac_leading = 0
else:
bias = self.characteristicClass.bias
frac_leading = 1
# acquire precise fraction, exponent (given that exponent must be
# representable in this class)
f_dec, e_dec = frexp(d, self)
e_dec += bias
# convert to binary and round to precision of significand
i = 0
max_bits = self.characteristicClass.exp_largest + \
self.significandClass.digits + 2
bfrac = npy.zeros(max_bits, int)
i_stop = max_bits
not_seen_one = True
# make sure we use extra precision (given that might adjust for
# leading 0's)
while f_dec > 0 and i < max_bits:
f_dec *= 2
bit = int(f_dec)
if not_seen_one and bit == 1:
# first time seen a 1 know that we only need up to
# significand's more digits + 2 (speed optimization)
i_stop = i + self.significandClass.digits + 2
not_seen_one = False
bfrac[i] = bit
f_dec -= bit
i += 1
if i >= i_stop:
break
# binary string representation of fraction to full precision
f = ''.join([str(b) for b in bfrac[:i]])
if frac_leading == 1:
try:
one_pos = f.index('1')
except ValueError:
# f is all 0's
pass
else:
# adjust for leading zeros
f = f[one_pos+1:]
e_dec -= one_pos+1
c = decint2binstr(e_dec)
else:
# denormalized, inevitable lost precision in f
c = '0'*self.characteristicClass.digits
f = '0'*abs(e_dec) + f
#print(len(f), f)
#print(2**Decimal(e_dec - bias) * (frac_leading + binfracstr2decfrac(f)))
round_dir = None # default
if len(c) < self.characteristicClass.digits:
c = pad(c, self.characteristicClass.digits)
if len(f) < self.significandClass.digits:
f = pad(f, self.significandClass.digits, to_right=True)
elif len(f) > self.significandClass.digits:
next_bit = int(f[self.significandClass.digits])
try:
remaining_bits = f[self.significandClass.digits+1:]
except IndexError:
remaining_bits = '0'
else:
if remaining_bits == '':
remaining_bits = '0'
round_dir = self._round(int(s), next_bit, int(remaining_bits) > 0)
f = f[:self.significandClass.digits]
self.signbit = SignBit(s)
self.characteristic = self.characteristicClass(c)
self.significand = self.significandClass(f)
return round_dir
def _round(self, sign, next_bit, non_zero_remainder):
r = self.round_mode
if r == ROUND_DOWN:
return None
elif r == ROUND_UP:
if next_bit == 1 or non_zero_remainder:
# > 0.0b in magnitude
if sign == 0:
return 'next'
else:
return 'prev'
else:
return None
elif r == ROUND_HALF_UP:
if next_bit == 1:
# >= 0.1b in magnitude, round away from 0
if sign == 0:
return 'next'
else:
return 'prev'
else:
return None
elif r == ROUND_HALF_DOWN:
if next_bit == 1:
if non_zero_remainder:
# > 0.1b in magnitude, round away from 0
if sign == 0:
return 'next'
else:
return 'prev'
else:
# = 0.1b, round towards 0
return None
else:
return None
elif r == ROUND_CEILING:
if sign == 0:
if next_bit == 1 or non_zero_remainder:
# > 0.0b in magnitude, round towards +inf
return 'next'
else:
return None
else:
return None
elif r == ROUND_FLOOR:
if sign == 1:
if next_bit == 1 or non_zero_remainder:
# > 0.0b in magnitude, round towards +inf
return 'prev'
else:
return None
else:
return None
else:
raise ValueError("Invalid rounding type")
def is_denormalized(self):
return self.dec_value <= self.largest_denorm
def as_tuple(self):
return self.tuple_rep
def as_binary(self):
"""Lazy evaluation of bin_value in case it's never used.
This is done because the calculation is slow!
"""
if self.bin_value == "":
self.bin_value = dec2binstr(self.dec_value, self)
return Binary(self.bin_value, self.__class__)
def as_decimal(self):
return self.dec_value
def __repr__(self):
if self.bin_value == "":
self.bin_value = dec2binstr(self.dec_value, self)
return 'Binary("%s", (%s, %s, %s))' % \
(self.bin_value,
self.characteristicClass.digits,
self.significandClass.digits,
self.round_mode)
def __str__(self):
return "%s %s %s" % (str(self.signbit), str(self.characteristic),
str(self.significand))
def next(self):
"""Return the successive representable float value in the more
positive direction.
"""
if self.signbit.dec_value == 1:
method = 'prev'
else:
method = 'next'
return self._step(method)
def prev(self):
"""Return the successive representable float value in the more
negative direction.
"""
if self.signbit.dec_value == 0:
method = 'prev'
else:
method = 'next'
return self._step(method)
def _step(self, method):
"""
Internal method for stepping to successive representable values,
either in the increasing or decreasing direction, according to the
method passed.
"""
# default value, unless switches
new_signbit = self.signbit
try:
new_signif = getattr(self.significand, method)()
except BinaryNegativeValue:
# CARRY
try:
new_char = getattr(self.characteristic, method)()
except BinaryNegativeValue:
# CHANGE SIGN
new_char = self.characteristic
new_signbit = SignBit(str(1-self.signbit.dec_value))
# have to reset new_signif in this case
new_signif = self.significandClass(pad('1',
self.significand.digits))
except BinaryOverflow:
raise ValueError("No more representable values")
else:
new_signif = self.significandClass('1'*self.significand.digits)
except BinaryOverflow:
# CARRY
# representation of one in the same number of significant digits
new_signif = self.significandClass('0'*self.significand.digits)
try:
new_char = getattr(self.characteristic, method)()
except BinaryOverflow:
raise ValueError("No more representable values")
else:
new_char = self.characteristic
return self.__class__(" ".join([str(new_signbit), str(new_char),
str(new_signif)]))
def _op_check(self, other):
try:
# may be another ContextClass with matching precision
other_sig_digits = other.significand.digits
other_char_digits = other.characteristic.digits
except AttributeError:
# may be a Binary object with matching precision
try:
other_sig_digits = other.context.significandClass.digits
other_char_digits = other.context.characteristicClass.digits
except AttributeError:
raise TypeError("Invalid Context object")
else:
ox = other.dec
c = other.context
else:
ox = other.dec_value
c = other
if other_sig_digits != self.significand.digits or \
other_char_digits != self.characteristic.digits:
raise ValueError("Mismatched precision")
elif c.round_mode != self.round_mode:
raise ValueError("Mismatched rounding modes")
return ox
def __eq__(self, other):
ox = self._op_check(other)
return self.dec_value == ox
def __ne__(self, other):
ox = self._op_check(other)
return self.dec_value != ox
def __le__(self, other):
ox = self._op_check(other)
return self.dec_value <= ox
def __lt__(self, other):
ox = self._op_check(other)
return self.dec_value < ox
def __ge__(self, other):
ox = self._op_check(other)
return self.dec_value >= ox
def __gt__(self, other):
ox = self._op_check(other)
return self.dec_value > ox
def __neg__(self):
return self.__class__(str(1-self.signbit.dec_value) + str(self)[1:])
def __abs__(self):
return self.__class__('0' + str(self)[1:])
def __add__(self, other):
ox = self._op_check(other)
return self.__class__(self.dec_value + ox)
__radd__ = __add__
def __sub__(self, other):
ox = self._op_check(other)
return self.__class__(self.dec_value - ox)
def __rsub__(self, other):
ox = self._op_check(other)
return self.__class__(ox - self.dec_value)
def __mul__(self, other):
ox = self._op_check(other)
return self.__class__(self.dec_value * ox)
__rmul__ = __mul__
def __div__(self, other):
ox = self._op_check(other)
return self.__class__(self.dec_value / ox)
def __rdiv__(self, other):
ox = self._op_check(other)
return self.__class__(ox / self.dec_value)
__rtruediv__ = __rdiv__
def __pow__(self, other):
ox = self._op_check(other)
return self.__class__(self.dec_value ** ox)
def __rpow__(self, other):
ox = self._op_check(other)
return self.__class__(ox ** self.dec_value)
def sqrt(self):
return self.__class__(self.dec_value.sqrt())
def __nonzero__(self):
return self.dec_value != 0
def max(self, other):
"""Respects NaN and Inf"""
ox = self._op_check(other)
r = self.dec_value.max(ox)
if r == self.dec_value:
return self
else:
return other
def min(self, other):
"""Respects NaN and Inf"""
ox = self._op_check(other)
r = self.dec_value.min(ox)
if r == self.dec_value:
return self
else:
return other
def __copy__(self):
if type(self) == ContextClass:
return self # I'm immutable; therefore I am my own clone
return self.__class__(str(self))
def __deepcopy__(self, memo):
if type(self) == ContextClass:
return self # My components are also immutable
return self.__class__(str(self))
class context_registry(object):
"""Registry saves re-creating temporary copies of the same context classes
during eval() calls, etc., by keeping a registry of all created
contexts in the present session.
"""
def __init__(self):
self.contexts = {}
def __call__(self, char_digits, sig_digits, rounding=ROUND_HALF_UP):
try:
return self.contexts[(char_digits, sig_digits, rounding)]
except KeyError:
return self.make_context(char_digits, sig_digits, rounding)
def make_context(self, char_digits, sig_digits, rounding):
"""Class factory for binary float arithmetic using the given number of
digits for the characteristic (exponent) and significand (mantissa, or
fraction).
rounding type defaults to rounding up (away from 0).
"""
if rounding not in _round_code:
raise ValueError("Invalid rounding type specified")
class CharacteristicClass(BinaryCharacteristic):
digits = char_digits
largest = 2**char_digits-1
bias = 2**(char_digits-1)-1
exp_largest = 2**(char_digits-1)
exp_lowest = -2**(char_digits-1) + 1
class SignificandClass(BinarySignificand):
digits = sig_digits
largest = 2**sig_digits-1
class context(ContextClass):
characteristicClass = CharacteristicClass
significandClass = SignificandClass
largest_denorm = (Decimal(2) ** Decimal(-2**(char_digits-1)+2) ) * \
(1 - Decimal(2)**(-sig_digits))
largest_norm = (1 - Decimal("0.5")**(sig_digits+1)) * \
2 ** (2**(char_digits - 1))
digits = 1 + char_digits + sig_digits
round_mode = rounding
Etop = 2**(char_digits-1)
Etiny = -2**(char_digits-1) + 1
context.__name__ = "Float_%d_%d_%s" % (char_digits, sig_digits,
_round_code[rounding])
self.contexts[(char_digits, sig_digits, rounding)] = context
return context
define_context = context_registry()
single = define_context(8, 23) # IEEE 754 32 bit float
double = define_context(11, 52) # IEEE 754 64 bit float
quadruple = define_context(15, 112) # IEEE 754 128 bit float
half = define_context(5, 10) # IEEE 754 16 bit float
test = define_context(4, 6, ROUND_DOWN) # for learning purposes
class Binary(object):
"""Constructor for binary floating point representation from a binary string
or Decimal class representation of a real number: e.g. -1101.100011 or
Decimal("-13.546875000"). The value may also be an instance of an existing
context.
If a context is given as a Context class, the return value will be an
instance of that class (a IEEE 754 representation of that binary value).
If the value given was from a different context it will be coerced.
The context may also be provided as the tuple: (characteristic_digits,
significand_digits,
rounding mode string)
although this is intended primarily for internal use or for eval(repr(x))
for a Context object x.
Binary() is the same as Binary('0') to be functionally compatible with
the Decimal class.
"""
def __init__(self, x='0', context=None):
if isinstance(context, tuple):
self.context = define_context(context[0], context[1],
rounding=context[2])
else:
self.context = context
# placeholder for representation of x
self.rep = None
if isinstance(x, Binary):
self.dec = x.dec
self.bin = x.bin
if context is None:
self.context = x.context
# in case x.context is not None (otherwise gets
# overwritten by x.dec == x.rep anyway)
self.rep = x.rep
elif isinstance(x, ContextClass):
if context is None:
# optimization -- don't recreate self.rep later
# from this context, keep it now.
self.context = x.__class__
self.rep = x
self.dec = x.as_decimal()
self.bin = str(x.as_binary())
else:
x_dec = x.as_decimal()
if abs(x_dec) > self.context.largest_norm:
if x_dec < 0:
self.bin = "-Inf"
seld.dec = Decimal("-Inf")
else:
self.bin = "Inf"
self.dec = Decimal("Inf")
self.rep = self.context(self.dec)
elif isinstance(x, Decimal):
self.dec = x
if self.context is None:
raise ValueError("Cannot create arbitrary precision binary "
"value without a representation context")
if x._isnan() or x._isinfinity() != 0:
if x._isnan():
self.bin = "NaN"
else:
if x < 0:
self.bin = "-Inf"
else:
self.bin = "Inf"
elif abs(x) > self.context.largest_norm:
if x < 0:
self.dec = Decimal("-Inf")
self.bin = "-Inf"
else:
self.dec = Decimal("Inf")
self.bin = "Inf"
else:
self.bin = dec2binstr(x, self.context)
else:
# string
bstr = x.lower()
if bstr in ['-inf', 'inf', 'nan']:
self.bin = x
self.dec = Decimal(x)
else:
self.bin = x
self.dec = binvalstr2dec(bstr)
if self.context is None:
self.rep = self.dec
else:
if self.rep is None:
try:
self.rep = self.context(self.dec)
except BinaryOverflow:
if self.dec < 0:
self.dec = Decimal("-Inf")
self.bin = "-Inf"
else:
self.dec = Decimal("Inf")
self.bin = "Inf"
self.rep = self.context(self.dec)
self.dec = self.rep.dec_value
if self.rep.bin_value == "":
# lazy evaluation hasn't been performed yet
self.bin = dec2binstr(self.dec, self.rep)
# might as well update the representation
self.rep.bin_value = self.bin
else:
self.bin = self.rep.bin_value
def __hash__(self):
return hash(self.rep)
def __str__(self):
return self.bin
def __repr__(self):
if self.context:
return 'Binary("%s", (%d, %d, %s))' % (self.bin,
self.context.characteristicClass.digits,
self.context.significandClass.digits,
self.context.round_mode)
else:
return 'Binary("%s")' % self.bin
def as_binary(self):
return self
def as_decimal(self):
return self.dec
def _op_check(self, other):
if isinstance(other, (int, long, npy.int32, npy.int64)):
ox, c = Decimal(other), None
elif isinstance(other, Binary):
ox, c = other.dec, other.context
elif isinstance(other, Decimal):
ox, c = other, None
elif isinstance(other, ContextClass):
# ContextClass is strict about comparing only with others
# of the same representation, so ensure self is
if self.context == other.__class__:
ox, c = other.as_decimal(), other.__class__
else:
raise TypeError("Invalid object for comparison")
else:
raise TypeError("Invalid object for comparison")
if self.context:
s_digits = self.context.digits
else:
s_digits = 0
if c:
c_digits = c.digits
else:
c_digits = 0
if s_digits > c_digits:
ctx = self.context
else:
if s_digits > 0 and s_digits == c_digits:
# contexts have the same precision, but what about
# rounding?
if self.context.round_mode == c.round_mode:
ctx = c
else:
raise ValueError("Clash of rounding modes for "
"equal-precision comparison")
else:
ctx = c
return ox, ctx
def __eq__(self, other):
ox, c = self._op_check(other)
return self.dec == ox
def __ne__(self, other):
ox, c = self._op_check(other)
return self.dec != ox
def __le__(self, other):
ox, c = self._op_check(other)
return self.dec <= ox
def __lt__(self, other):
ox, c = self._op_check(other)
return self.dec < ox
def __ge__(self, other):
ox, c = self._op_check(other)
return self.dec >= ox
def __gt__(self, other):
ox, c = self._op_check(other)
return self.dec > ox
def __neg__(self):
return self.__class__(-self.rep)
def __abs__(self):
return self.__class__(abs(self.rep))
def __add__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(self.dec + ox, ctx)
__radd__ = __add__
def __sub__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(self.dec - ox, ctx)
def __rsub__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(ox - self.dec, ctx)
def __mul__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(self.dec * ox, ctx)
__rmul__ = __mul__
def __div__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(self.dec / ox, ctx)
def __rdiv__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(ox / self.dec, ctx)
__rtruediv__ = __rdiv__
def __pow__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(self.dec ** ox, ctx)
def __rpow__(self, other):
ox, ctx = self._op_check(other)
return self.__class__(ox ** self.dec, ctx)
def __nonzero__(self):
return self.dec != 0
def sqrt(self):
return self.__class__(self.dec.sqrt(), self.context)
def max(self, other):
"""Respects NaN and Inf"""
ox, ctx = self._op_check(other)
r = self.dec.max(ox)
if r == self.dec:
return self
else:
return other
def min(self, other):
"""Respects NaN and Inf"""
ox, ctx = self._op_check(other)
r = self.dec.min(ox)
if r == self.dec:
return self
else:
return other
def __reduce__(self):
return (self.__class__, (repr(self),))
def __copy__(self):
if type(self) == Binary:
return self # I'm immutable; therefore I am my own clone
return self.__class__(str(self))
def __deepcopy__(self, memo):
if type(self) == Binary:
return self # My components are also immutable
return self.__class__(str(self))
def binvalstr2dec(x):
"""Convert signed real numbers in binary string form to decimal
value (no special values Inf, NaN), including values in scientific notation.
"""
if not isbinstr(x):
raise ValueError("Invalid string representation of binary"
" float: %s" % x)
if x[0] == '-':
x = x[1:]
sign = -1
else:
sign = 1
if 'e' in x:
x, estr = x.split('e')
e = int(estr)
elif 'E' in x:
x, estr = x.split('E')
e = int(estr)
else:
e = 0
if '.' in x:
try:
whole, frac = x.split('.')
except ValueError:
raise ValueError("Invalid string representation of binary"
" float")
else:
if frac == "":
frac = '0'
if whole == "":
whole = '0'
else:
whole = x
frac = '0'
try:
dec_whole = Decimal(int(whole, base=2)) * Decimal(2)**e
except ValueError:
dec_whole = Decimal(0)
dec_frac = binfracstr2decfrac(frac) * Decimal(2)**e
return sign*(dec_whole+dec_frac)
def isbinstr(arg):
# supports unary + / - at front, and checks for usage of exponentials
# (using 'E' or 'e')
s = arg.lower()
try:
if s[0] in ['+','-']:
s_rest = s[1:]
else:
s_rest = s
except IndexError:
return False
if '0' not in s_rest and '1' not in s_rest:
return False
pts = s.count('.')
exps = s.count('e')
pm = s_rest.count('+') + s_rest.count('-')
if pts > 1 or exps > 1 or pm > 1:
return False
if exps == 1:
exp_pos = s.find('e')
pre_exp = s[:exp_pos]
# must be numbers before and after the 'e'
if not npy.sometrue([n in ('0','1') for n in pre_exp]):
return False
if s[-1]=='e':
# no chars after 'e'!
return False
if not npy.sometrue([n in ('0','1','2','3','4','5','6','7','8','9') \
for n in s[exp_pos:]]):
return False
# check that any additional +/- occurs directly after 'e'
if pm == 1:
pm_pos = max([s_rest.find('+'), s_rest.find('-')])
if s_rest[pm_pos-1] != 'e':
return False
e_rest = s_rest[pm_pos+1:] # safe due to previous check
s_rest = s_rest[:pm_pos+1]
else:
e_rest = s[exp_pos+1:]
s_rest = s[:exp_pos+1]
# only remaining chars in s after e and possible +/- are numbers
if '.' in e_rest:
return False
# cannot use additional +/- if not using exponent
if pm == 1 and exps == 0:
return False
return npy.alltrue([n in ('0', '1', '.', 'e', '+', '-') for n in s_rest])
def binstr2dec(bstr):
"""Convert binary string representation of an integer to a decimal integer.
"""
return int(bstr, base=2)
def decint2binstr(n):
"""Convert decimal integer to binary string.
"""
if n < 0:
return '-' + decint2binstr(-n)
s = ''
while n != 0:
s = str(n % 2) + s
n >>= 1
return s or '0'
def binfracstr2decfrac(bstr):
"""Convert non-negative binary string fraction (without radix) to decimal
fraction.
e.g. to convert ".1101", binfracstr2decfrac('1101') -> Decimal("0.8125")
"""
assert bstr[0] != '-', "Only pass non-negative values"
dec_value = 0
half = Decimal("0.5")
for place, bit in enumerate(bstr):
if int(bit) == 1:
dec_value += half**(place+1)
return dec_value
def frexp(d, context):
"""Implementation of 'frexp' for arbitrary precision decimals.
Result is a pair F, E, where 0 < F < 1 is a Decimal object,
and E is a signed integer such that
d = F * 2**E
context specifies the maximum absolute value of the exponent
to ensure termination of the calculation. e.g. for double precision
pass the 'double' Context class which defines a characteristic
of 11 bits, with maximum exponent size of 1024.
"""
e_largest = context.characteristicClass.exp_largest
if d < 0:
res = frexp(-d, e_largest)
return -res[0], res[1]
elif d == 0:
return Decimal("0"), 0
elif d >= 1:
w_dec = int(d)
e_dec = 0
while w_dec > 0 and abs(e_dec) <= e_largest:
d /= 2
w_dec = int(d)
e_dec += 1
return d, e_dec
else:
# 0 < d < 1
w_dec = 0
e_dec = 0
while w_dec == 0 and abs(e_dec) <= e_largest:
w_dec = int(d*2)
if w_dec > 0:
break
else:
d *= 2
e_dec -= 1
return d, e_dec
def dec2binstr(x, context=None):
if x < 0:
signstr = '-'
valstr = -x
elif x == 0:
return '0.0'
else:
signstr = ''
valstr = x
# convert to binary fraction representation
e, f = decfrac2binrep(valstr, context)
return signstr + '0.' + f + 'E' + str(int(e, base=2))
def decfrac2binrep(x, context):
"""Convert positive decimal float to the nearest representable
"<characteristic> <significand>" binary string representation (natural
format, where characteristic may be negative), using an exponent no bigger
than permitted in the given context.
"""
assert x > 0, "Provide only positive Decimal float value"
assert isinstance(x, Decimal), "Provide only positive Decimal float value"
fraction, exponent = frexp(x, context)
max_bits = context.characteristicClass.exp_largest + \
context.significandClass.digits
bfrac = npy.zeros(max_bits, int)
i = 0
i_stop = max_bits
not_seen_one = True
while fraction > 0 and i < max_bits:
fraction *= 2
bit = int(fraction)
if not_seen_one and bit == 1:
# speed optimization
i_stop = i + context.significandClass.digits + 2
bfrac[i] = bit
fraction -= bit
i += 1
if i >= i_stop:
break
# negative exponents OK for this usage
return decint2binstr(exponent), "".join([str(bit) for \
bit in bfrac[:i]])
def pad(value, digits, to_right=False):
"""Only use for positive binary numbers given as strings.
Pads to the left by default, or to the right using to_right flag.
Inputs: value -- string of bits
digits -- number of bits in representation
to_right -- Boolean, direction of padding
Output: string of bits of length 'digits'
Raises exception if value is larger than digits in length.
Example:
pad('0010', 6) -> '000010'
pad('0010', 6, True) -> '001000'
"""
len_val = len(value)
assert len_val <= digits
rem_digits = digits - len_val
if to_right:
return value + "0"*rem_digits
else:
return "0"*rem_digits + value
def bin_sign(x):
"""Binary representation of sign: x < 0 is represented as 1, 0 otherwise.
"""
s=npy.sign(x)
if s >= 0:
return 0
else:
return 1
## exceptions
class BinaryException(ArithmeticError):
def __init__(self, value=None):
self.value = value
self.code = None
def __str__(self):
return repr(self.value)
def __repr__(self):
return repr(self.value)
class BinaryOverflow(BinaryException):
pass
class BinaryUnderflow(BinaryException):
pass
class BinaryNegativeValue(BinaryException):
pass
class BinaryRemainderValue(BinaryException):
pass
"""Tests of binary floating point simulation
Author: Robert Clewley, August 2008.
Updated for Python 3 compatibility, 2015.
"""
import numpy as npy
import math
import decimal
from decimal import Decimal
from copy import copy, deepcopy
from simfloat import *
def display(bf, linefeed=False):
"""Pretty printer for binary float representations
"""
if linefeed:
print("Binary: %s\n Decimal: %s" % (repr(bf), repr(bf.as_decimal())))
else:
print("Binary: %s Decimal: %s" % (repr(bf), repr(bf.as_decimal())))
# Invalid literals
invalid = ["Binary('-0.111e-4.')", "Binary('-.11-e-5')", "Binary('.11.')",
"Binary('e-4')", "Binary('1e.4')", "Binary('0.111a3')",
"Binary('')", "Binary('.')", "Binary('-')", "Binary('-.')",
"Binary('0-')", "Binary(' 1')"]
for lit in invalid:
try:
eval(lit)
except ValueError:
pass
else:
print(lit)
raise ValueError("Invalid literal was accepted!")
valid = ["Binary('1.', double)", "Binary('.1')", "Binary()", "Binary('0')",
"Binary('-0')", "Binary('-1.e-1')", "Binary('001.110011')",
"Binary('11e01')"]
for lit in valid:
eval(lit)
zero_double = double(0)
smallest_denormalized_double = zero_double.next()
assert abs(smallest_denormalized_double.as_decimal() - Decimal("4.94e-324")) \
< Decimal("1e-327")
assert smallest_denormalized_double.as_decimal().adjusted() == -324
smallest_normalized_double = double(double.largest_denorm).next()
assert abs(smallest_normalized_double.as_decimal() - Decimal("2.23e-308")) \
< Decimal("1e-310")
assert smallest_normalized_double.as_decimal().adjusted() == - 308
assert double(npy.float64(1.3)) == double(Decimal("1.3"))
assert double(npy.float64(1.3e30)) == double(Decimal("1.3e30"))
assert double(npy.float64(1.3e-30)) == double(Decimal("1.3e-30"))
assert double(npy.float64(1.3e-305)) == double(Decimal("1.3e-305"))
i = npy.finfo(npy.double)
assert i.eps == float(double(i.eps).dec_value)
one = double(1)
next_one = one.next()
eps_double = next_one - one
assert float(eps_double.as_decimal()) == i.eps
z = single(0)
z1 = z.next()
z1m = z.prev()
z2 = z1.next()
z3 = z2.next()
print("Smallest representable non-negative value in IEEE single precision")
display(z1, 1)
print("Smallest representable non-positive value in IEEE single precision")
display(z1m, 1)
assert z1m == -z1 == -abs(z1m)
print("This x is precisely representable as a native IEEE double precision")
print(" 64-bit float: 25.56640625")
x_native = 25.56640625
assert float(double(x_native).as_decimal()) == x_native
x_bf = double(x_native)
assert --x_bf == x_bf
assert -abs(-x_bf)*double(-1) == x_bf
x_bf_next = x_bf.next()
x_bf_prev = x_bf.prev()
print("Initial value x:")
display(x_bf, 1)
print("Next representable value to x:")
display(x_bf_next, 1)
print("Previous presentable value to x:")
display(x_bf_prev, 1)
assert x_bf < x_bf_next
assert x_bf > x_bf_prev
assert float((x_bf ** double(2)).as_decimal()) == x_native**2
assert float(x_bf.sqrt().as_decimal()) == math.sqrt(x_native)
# Coercion
q = quadruple(Decimal("3.00005235236e350"))
print("\nQuadruple precision value closest to 3.00005235236e350:")
display(q, 1)
q_converted_double = Binary(q, double)
print("\nCoerced quadruple value to double precision representation:")
print("Internal binary overflow renders Inf result")
assert q_converted_double.as_decimal() == Decimal("Inf")
display(q_converted_double, 1)
# copying
assert copy(x_bf) == x_bf
assert deepcopy(x_bf) == x_bf
print("\n\nShowing all values in a 6-bit (1,2,2) representation:")
con_1_2_2 = define_context(2, 2)
s = con_1_2_2('01111')
while True:
display(s)
try:
s=s.prev()
except ValueError, e:
print(e)
break
print("\nGoing back the other way... (note the -0 instead of 0)")
while True:
display(s)
try:
s=s.next()
except ValueError, e:
print(e)
break
print("\nTesting denormalized number representations...")
tiny_double = double('0' + '0'*10 + '1' + '0'*52)
assert i.tiny == float(tiny_double.as_decimal())
tiny2_double = double(str(tiny_double))
assert tiny2_double == tiny_double
tiny_double_prev = tiny_double.prev()
assert tiny_double_prev.is_denormalized()
assert tiny_double_prev == double('0' + '0'*11 + '1'*52)
try:
assert npy.isinf(float(double(1.2e600).as_decimal()))
except ValueError:
print("If this is python 2.5, cannot create float instances from string")
print(" literals 'infinity', 'nan', etc.")
pass
print("\n\nInf in IEEE 754 single precision mode:")
single_inf = single(' '.join(['0', '1'*8, '0'*23]))
display(single_inf, 1)
try:
print(single_inf > tiny_double)
except ValueError, e:
print("\nsingle_inf > tiny_double ??")
print(" Exception raised: %s" % e + " (cannot compare floats of different"\
" precisions)" )
else:
raise ValueError("Comparison failed!")
print("\n\nComparing representations of 1/10 ...")
n01_dec = Decimal("0.1")
n01_double = double(0.1)
x = n01_double.as_decimal()
print("in IEEE 754 double: %s" % str(x) + "\n rounding error = %s" % repr(x - n01_dec))
n01_single = single(0.1)
x = n01_single.as_decimal()
print("in IEEE 754 single: %s" % str(x) + "\n rounding error = %s" % repr(x - n01_dec))
bf = define_context(8, 8)
n01_bf = bf(0.1)
x = n01_bf.as_decimal()
print("in a (1,8,8) representation: %s" % str(x) + \
"\n rounding error = %s" % repr(x - n01_dec))
b1 = Binary('-.000011010110', double)
b2 = Binary('-0.011', double)
b_sum = double(b1.as_decimal()+b2.as_decimal())
b_sum2 = b1 + b2
assert b_sum == b_sum2
assert b_sum2 == b_sum # not the same __eq__ method called!
print("\n\nb1 =")
display(b1, 1)
print("b2 =")
display(b2, 1)
print("b1 + b2 =")
display(b_sum, 1)
# mixed type arithmetic involving Binary objects
b1_dec = b1.as_decimal()
b1_double = double(b1_dec)
b1_re = eval(repr(b1_double))
assert b1_re.context == b1_double.context
b_sum3 = b1_dec + b2
assert b_sum3 == b_sum
b_sum4 = b1_double + b2
assert b_sum4 == b_sum
b_sum5 = b2 + b1_dec
assert b_sum4 == b_sum
b_sum6 = b2 + b1_double
assert b_sum6 == b_sum
try:
b_sum7 = b2 + float(b1_dec)
except TypeError:
# invalid comparison
pass
else:
raise "Invalid comparison succeeded!"
# complicated checks to make sure everything cross-references OK
assert b1 == Binary(double(b1_dec))
assert b1 == Binary(b1.as_binary())
assert b1 == Binary(b1)
assert b1 == b1_re
assert b1_double.as_binary() == b1
# mismatched precision, but Binary values can be compared (just not added, etc.)
assert b1 == Binary(b1, single)
b1_fromrepr = eval(repr(b1))
assert b1.as_decimal() == Decimal("-0.05224609375")
assert b1_fromrepr == b1
print("\n\nb1 in binary fraction form = %s" % str(b1.as_binary()))
print("6th digit of b1's representation's significand is %s " % b1.rep.significand[6-1])
print("\n\nRounding mode tests on a (1,2,2) context")
r_half_up = define_context(2, 2, rounding=ROUND_HALF_UP)
r_up = define_context(2, 2, rounding=ROUND_UP)
r_down = define_context(2, 2, rounding=ROUND_DOWN)
r_half_down = define_context(2, 2, rounding=ROUND_HALF_DOWN)
r_ceiling = define_context(2, 2, rounding=ROUND_CEILING)
r_floor = define_context(2, 2, rounding=ROUND_FLOOR)
expected_results = {
r_half_up: {
'0.0100': '0.01',
'0.0101': '0.01',
'0.0110': '0.1',
'0.0010': '0.01',
'0.0011': '0.01',
'-0.0100': '-0.01',
'-0.0101': '-0.01',
'-0.0110': '-0.1',
'-0.0010': '-0.01',
'-0.0011': '-0.01'},
r_up: {
'0.0100': '0.01',
'0.0101': '0.1',
'0.0110': '0.1',
'0.0010': '0.01',
'0.0011': '0.01',
'-0.0100': '-0.01',
'-0.0101': '-0.1',
'-0.0110': '-0.1',
'-0.0010': '-0.01',
'-0.0011': '-0.01'},
r_down: {
'0.0100': '0.01',
'0.0101': '0.01',
'0.0110': '0.01',
'0.0010': '0.0',
'0.0011': '0.0',
'-0.0100': '-0.01',
'-0.0101': '-0.01',
'-0.0110': '-0.01',
'-0.0010': '-0.0',
'-0.0011': '-0.0'},
r_half_down: {
'0.0100': '0.01',
'0.0101': '0.01',
'0.0110': '0.01',
'0.0010': '0.0',
'0.0011': '0.01',
'-0.0100': '-0.01',
'-0.0101': '-0.01',
'-0.0110': '-0.01',
'-0.0010': '-0.0',
'-0.0011': '-0.01'},
r_ceiling: {
'0.0100': '0.01',
'0.0101': '0.1',
'0.0110': '0.1',
'0.0010': '0.01',
'0.0011': '0.01',
'-0.0100': '-0.01',
'-0.0101': '-0.01',
'-0.0110': '-0.01',
'-0.0010': '-0.0',
'-0.0011': '-0.0'},
r_floor: {
'0.0100': '0.01',
'0.0101': '0.01',
'0.0110': '0.01',
'0.0010': '0.0',
'0.0011': '0.0',
'-0.0100': '-0.01',
'-0.0101': '-0.1',
'-0.0110': '-0.1',
'-0.0010': '-0.01',
'-0.0011': '-0.01'}
}
for con in (r_half_up, r_up, r_down, r_half_down, r_ceiling, r_floor):
print("\nTesting rounding mode %s:" % con.round_mode.lower())
expected = expected_results[con]
for in_val, out_val in expected.items():
rb = Binary(in_val, con)
print(" Value %s -> %s" % (in_val, out_val))
assert rb == Binary(out_val)
try:
r32 = r_half_up(1.6) + r_down(1.6)
except ValueError:
# mismatched rounding modes despite same precision
pass
else:
raise "Mismatched rounding modes added OK!"
print("\n\nUp-conversion test:")
rb = Binary('0.01', r_ceiling)
qrb = quadruple(rb)
qrb2 = Binary(rb, quadruple)
display(qrb, 1)
assert qrb == qrb2
print("\nConverted (2,2,CEILING) representation of 0.01b to quadruple precision:")
print("%s -> %s" % (repr(rb), repr(qrb)))
xs = Binary('-1111.001', single)
xd1 = Binary(xs, double)
xd2 = Binary(double(xs))
assert xd1 == xd2
assert xd1/xd2 == double(1)
bd = Binary(double(1.5))
bq = Binary(quadruple(0.1))
c = bd + bq
assert c - bd == bq
assert (c * bd)/bd == c
assert x_bf == x_bf.max(x_bf_prev)
assert rb == rb.min(rb-b1)
assert eval(repr(double(npy.inf))) == double(npy.inf)
print("\n\n** All tests passed.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment