[pypy-commit] pypy propogate-nans: do bit manipulation to preserve, where possible, mantissa values in nan floating point conversions
mattip
noreply at buildbot.pypy.org
Fri Nov 6 06:29:13 EST 2015
Author: mattip <matti.picus at gmail.com>
Branch: propogate-nans
Changeset: r80563:f9a88e6a3a0f
Date: 2015-11-06 13:20 +0200
http://bitbucket.org/pypy/pypy/changeset/f9a88e6a3a0f/
Log: do bit manipulation to preserve, where possible, mantissa values in
nan floating point conversions
diff --git a/pypy/module/micronumpy/test/test_ndarray.py b/pypy/module/micronumpy/test/test_ndarray.py
--- a/pypy/module/micronumpy/test/test_ndarray.py
+++ b/pypy/module/micronumpy/test/test_ndarray.py
@@ -1853,14 +1853,14 @@
assert a.view('S16')[0] == '\x01' + '\x00' * 7 + '\x02'
def test_half_conversions(self):
+ # numpy preserves value for uint16 -> cast_as_float16 ->
+ # convert_to_float64 -> convert_to_float16 -> uint16
+ # even for float16 various float16 nans
from numpy import array, arange
- all_f16 = arange(0xff10, 0xff20, dtype='uint16')
+ all_f16 = arange(0xfe00, 0xfe08, dtype='uint16')
all_f16.dtype = 'float16'
- print all_f16.view(dtype='uint16')
all_f32 = array(all_f16, dtype='float32')
- print all_f32.view(dtype='uint32')
b = array(all_f32, dtype='float16')
- print b.view(dtype='uint16')
c = b.view(dtype='uint16')
d = all_f16.view(dtype='uint16')
assert (c == d).all()
diff --git a/rpython/rlib/rstruct/ieee.py b/rpython/rlib/rstruct/ieee.py
--- a/rpython/rlib/rstruct/ieee.py
+++ b/rpython/rlib/rstruct/ieee.py
@@ -5,8 +5,8 @@
import math
from rpython.rlib import rarithmetic, rfloat, objectmodel, jit
-from rpython.rlib.rarithmetic import r_ulonglong
-from rpython.rtyper.lltypesystem.rffi import DOUBLE, cast
+from rpython.rtyper.lltypesystem.rffi import r_ulonglong, ULONGP, DOUBLEP, cast
+from rpython.rtyper.lltypesystem import lltype
def round_to_nearest(x):
"""Python 3 style round: round a float x to the nearest int, but
@@ -64,7 +64,16 @@
if mant == 0:
result = rfloat.INFINITY
else:
- result = rfloat.NAN #cast(DOUBLE, mant |
+ # preserve mant value but pad w/zeros
+ exp = 0x7ff << 52
+ sign = sign << 63
+ mant = mant << (53 - MANT_DIG)
+ b = lltype.malloc(ULONGP.TO, 1, flavor='raw')
+ b[0] = r_ulonglong(exp) | r_ulonglong(mant) | r_ulonglong(sign)
+ #b = cast(ULONGP, r_ulonglong(exp | mant | sign))
+ result = cast(DOUBLEP, b)[0]
+ lltype.free(b, flavor='raw')
+ return result
elif exp == 0:
# subnormal or zero
result = math.ldexp(mant, MIN_EXP - MANT_DIG)
@@ -76,7 +85,7 @@
def float_unpack80(QQ, size):
'''Unpack a (mant, exp) tuple of r_ulonglong in 80-bit extended format
- into a long double float
+ into a python float (a double)
'''
if size == 10 or size == 12 or size == 16:
MIN_EXP = -16381
@@ -104,7 +113,16 @@
if exp == MAX_EXP - MIN_EXP + 2:
# nan or infinity
- result = rfloat.NAN if mant &((one << MANT_DIG - 1) - 1) else rfloat.INFINITY
+ if mant == 0:
+ result = rfloat.INFINITY
+ else:
+ exp = 0x7ff << 52
+ b = lltype.malloc(ULONGP.TO, 1, flavor='raw')
+ sign = sign << 63
+ b[0] = r_ulonglong(exp) | r_ulonglong(mant) | r_ulonglong(sign)
+ result = cast(DOUBLEP, b)[0]
+ lltype.free(b, flavor='raw')
+ return result
else:
# normal
result = math.ldexp(mant, exp + MIN_EXP - MANT_DIG - 1)
@@ -132,13 +150,21 @@
raise ValueError("invalid size value")
sign = rfloat.copysign(1.0, x) < 0.0
- if not rfloat.isfinite(x):
- if rfloat.isinf(x):
- mant = r_ulonglong(0)
- exp = MAX_EXP - MIN_EXP + 2
- else: # rfloat.isnan(x):
- mant = r_ulonglong(1) << (MANT_DIG-2) # other values possible
- exp = MAX_EXP - MIN_EXP + 2
+ if rfloat.isinf(x):
+ mant = r_ulonglong(0)
+ exp = MAX_EXP - MIN_EXP + 2
+ elif rfloat.isnan(x):
+ b = lltype.malloc(DOUBLEP.TO, 1, flavor='raw')
+ b[0] = x
+ asint = cast(ULONGP, b)[0]
+ mant = asint & ((r_ulonglong(1) << 52) - 1)
+ sign = asint >> 63
+ lltype.free(b, flavor='raw')
+ # shift off lower bits, perhaps losing data
+ mant = mant >> (53 - MANT_DIG)
+ if mant == 0:
+ mant = r_ulonglong(1) << (MANT_DIG-2)
+ exp = MAX_EXP - MIN_EXP + 2
elif x == 0.0:
mant = r_ulonglong(0)
exp = 0
@@ -171,7 +197,7 @@
# check constraints
if not objectmodel.we_are_translated():
- assert 0 <= mant < 1 << MANT_DIG - 1
+ assert 0 <= mant <= (1 << MANT_DIG) - 1
assert 0 <= exp <= MAX_EXP - MIN_EXP + 2
assert 0 <= sign <= 1
exp = r_ulonglong(exp)
@@ -191,13 +217,17 @@
raise ValueError("invalid size value")
sign = rfloat.copysign(1.0, x) < 0.0
- if not rfloat.isfinite(x):
- if rfloat.isinf(x):
- mant = r_ulonglong(0)
- exp = MAX_EXP - MIN_EXP + 2
- else: # rfloat.isnan(x):
- mant = (r_ulonglong(1) << (MANT_DIG-2)) - 1 # other values possible
- exp = MAX_EXP - MIN_EXP + 2
+ if rfloat.isinf(x):
+ mant = r_ulonglong(0)
+ exp = MAX_EXP - MIN_EXP + 2
+ elif rfloat.isnan(x): # rfloat.isnan(x):
+ b = lltype.malloc(DOUBLEP.TO, 1, flavor='raw')
+ b[0] = x
+ asint = cast(ULONGP, b)[0]
+ mant = asint & ((r_ulonglong(1) << 52) - 1)
+ lltype.free(b, flavor='raw')
+ sign = asint >> 63
+ exp = MAX_EXP - MIN_EXP + 2
elif x == 0.0:
mant = r_ulonglong(0)
exp = 0
@@ -225,12 +255,12 @@
if exp >= MAX_EXP - MIN_EXP + 2:
raise OverflowError("float too large to pack in this format")
+ mant = mant << 1
# check constraints
if not objectmodel.we_are_translated():
- assert 0 <= mant < 1 << MANT_DIG - 1
+ assert 0 <= mant <= (1 << MANT_DIG) - 1
assert 0 <= exp <= MAX_EXP - MIN_EXP + 2
assert 0 <= sign <= 1
- mant = mant << 1
exp = r_ulonglong(exp)
sign = r_ulonglong(sign)
return (mant, (sign << BITS - MANT_DIG - 1) | exp)
diff --git a/rpython/rlib/rstruct/test/test_ieee.py b/rpython/rlib/rstruct/test/test_ieee.py
--- a/rpython/rlib/rstruct/test/test_ieee.py
+++ b/rpython/rlib/rstruct/test/test_ieee.py
@@ -168,15 +168,31 @@
def test_random(self):
# construct a Python float from random integer, using struct
+ mantissa_mask = (1 << 53) - 1
for _ in xrange(10000):
Q = random.randrange(2**64)
x = struct.unpack('<d', struct.pack('<Q', Q))[0]
# nans are tricky: we can't hope to reproduce the bit
- # pattern exactly, so check_float will fail for a random nan.
- if isnan(x):
+ # pattern exactly, so check_float will fail for a nan
+ # whose mantissa does not fit into float16's mantissa.
+ if isnan(x) and (Q & mantissa_mask) >= 1 << 11:
continue
self.check_float(x)
+ def test_various_nans(self):
+ # check patterns that should preserve the mantissa across nan conversions
+ maxmant64 = (1 << 52) - 1 # maximum double mantissa
+ maxmant16 = (1 << 10) - 1 # maximum float16 mantissa
+ assert maxmant64 >> 42 == maxmant16
+ exp = 0xfff << 52
+ for i in range(20):
+ val_to_preserve = exp | ((maxmant16 - i) << 42)
+ a = ieee.float_unpack(val_to_preserve, 8)
+ assert isnan(a), 'i %d, maxmant %s' % (i, hex(val_to_preserve))
+ b = ieee.float_pack(a, 8)
+ assert b == val_to_preserve, 'i %d, val %s b %s' % (i, hex(val_to_preserve), hex(b))
+ b = ieee.float_pack(a, 2)
+ assert b == 0xffff - i, 'i %d, b%s' % (i, hex(b))
class TestCompiled:
def test_pack_float(self):
More information about the pypy-commit
mailing list