tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

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()