float_ieee_test.py (5005B)
1 #!/usr/bin/env python3 2 import ctypes 3 import platform 4 import struct 5 import unittest 6 7 from float_ieee import * 8 9 10 def host_word(x):return int.from_bytes(struct.pack('>d',x),'big') 11 def host_float(w):return struct.unpack('>d',w.to_bytes(8,'big'))[0] 12 13 14 def host_check(): 15 """Linux x86-64 fenv constants; zero-divisor Python exceptions are excluded. 16 17 Read flags immediately after the host operation, BEFORE formatting or oracle 18 arithmetic. NaN bits are checked by class only: payload policy is our own. 19 """ 20 if platform.system()!='Linux' or platform.machine()!='x86_64': 21 raise unittest.SkipTest('fenv probe is explicitly Linux x86-64 only') 22 lib=ctypes.CDLL(None) 23 try:clear,test,rounding=lib.feclearexcept,lib.fetestexcept,lib.fegetround 24 except AttributeError:raise unittest.SkipTest('fenv unavailable') 25 clear.argtypes=test.argtypes=[ctypes.c_int];clear.restype=test.restype=rounding.restype=ctypes.c_int 26 if rounding()!=0:raise unittest.SkipTest('host is not in nearest rounding mode') 27 counts={'checked':0,'zero_divisors_excluded':0,'nan_class_only':0} 28 try: 29 for op in OPS: 30 for a,b in cases(op): 31 if op=='divides' and (b&MASK)==0: 32 counts['zero_divisors_excluded']+=1;continue 33 fa,fb=host_float(a),host_float(b) 34 assert clear(0x3d)==0 35 if op=='adds':result=fa+fb 36 elif op=='subtracts':result=fa-fb 37 elif op=='multiplies':result=fa*fb 38 else:result=fa/fb 39 flags=test(0x3d) 40 actual=host_word(result) 41 ok,(wanted,wanted_flags)=oracle(a,b,op);assert ok 42 expected_flags=sum(mask for bit,mask in zip(wanted_flags,[1,4,8,16,32]) if bit) 43 assert flags==expected_flags,(op,hex(a),hex(b),'flags',hex(flags),hex(expected_flags)) 44 if nan(wanted): 45 assert nan(actual) and actual&QUIET 46 counts['nan_class_only']+=1 47 else:assert actual==wanted,(op,hex(a),hex(b),hex(actual),hex(wanted)) 48 counts['checked']+=1 49 finally:clear(0x3d) 50 return counts 51 52 53 class IEEETests(unittest.TestCase): 54 def test_leftmost_nan_and_signaling_anywhere(self): 55 left=SIGN|QNAN|123;right=INF|456 56 for op in OPS: 57 self.assertEqual(oracle(left,right,op),success(left,invalid=True)) 58 self.assertEqual(oracle(right,left,op),success(right|QUIET,invalid=True)) 59 self.assertEqual(oracle(left,QNAN|456,op),success(left)) 60 # Subtraction preserves the propagated right NaN's sign. 61 self.assertEqual(oracle(0,SIGN|right,op),success(SIGN|right|QUIET,invalid=True)) 62 63 def test_invalid_without_nan_is_canonical(self): 64 for a,b,op in [(0,INF,'multiplies'),(SIGN,INF|SIGN,'multiplies'), 65 (INF,INF|SIGN,'adds'),(INF,INF,'subtracts'), 66 (0,0,'divides'),(INF,INF,'divides')]: 67 self.assertEqual(oracle(a,b,op),success(QNAN,invalid=True)) 68 69 def test_division_classes_and_flags(self): 70 self.assertEqual(oracle(0x3ff0000000000000,SIGN,'divides'),success(INF|SIGN,dz=True)) 71 self.assertEqual(oracle(INF,SIGN,'divides'),success(INF|SIGN)) 72 self.assertEqual(oracle(SIGN,INF,'divides'),success(SIGN)) 73 self.assertEqual(oracle(SIGN,0,'divides'),success(QNAN,invalid=True)) 74 75 def test_ieee_tininess_not_stored_exponent(self): 76 # Storage rounds to minimum normal. Precision rounding with an unbounded 77 # exponent stays tiny, so underflow and inexact are both required. 78 self.assertEqual(oracle(MIN_NORMAL,0x3fefffffffffffff,'multiplies'), 79 success(MIN_NORMAL,uf=True,ix=True)) 80 self.assertEqual(oracle(0x001fffffffffffff,0x4000000000000000,'divides'), 81 success(MIN_NORMAL,uf=True,ix=True)) 82 a=(511<<52)|(((2**27-1)<<26)-2**52) 83 b=(512<<52)|(((2**27+1)<<25)-2**52) 84 self.assertEqual(finite_value(a)*finite_value(b),TINY_BOUNDARY) 85 self.assertEqual(oracle(a,b,'multiplies'),success(MIN_NORMAL,ix=True)) 86 for s,n in direct_cases(): 87 cut=max(n.bit_length()-53,0);q,r=divmod(n,2**cut) 88 q+=2*r>2**cut or (2*r==2**cut and q%2==1) 89 unbounded=dyadic(q,s+cut-2304) 90 self.assertEqual(unbounded<value(MIN_NORMAL),dyadic(n,s-2304)<TINY_BOUNDARY) 91 92 def test_full_flags_vs_host(self): 93 counts=host_check();self.assertGreater(counts['checked'],10000) 94 95 def test_flags_are_independent(self): 96 self.assertEqual(oracle(0x7fefffffffffffff,0x4000000000000000,'multiplies'),success(INF,of=True,ix=True)) 97 self.assertEqual(oracle(SIGN|1,0x3fe0000000000000,'multiplies'),success(SIGN,uf=True,ix=True)) 98 self.assertEqual(oracle(2,0x3fe0000000000000,'multiplies'),success(1)) 99 for op in OPS:self.assertEqual(oracle(LIMIT,INF|1,op),(False,2)) 100 101 102 if __name__=='__main__':unittest.main()