float_chains_test.py (3502B)
1 #!/usr/bin/env python3 2 import hashlib 3 import os 4 from pathlib import Path 5 import subprocess 6 import unittest 7 8 from float_chains import cases, groups, chain_oracle 9 from float_ieee import OPS, SIGN, INF, QNAN, MIN_NORMAL, QUIET, LIMIT, nan, oracle 10 11 12 def host_trace(reference): 13 rows=[];expected=[] 14 for initial,pool,steps in cases(): 15 word=initial 16 for op,index in steps: 17 rows.append(f'{op} {word:016x} {pool[index]:016x}\n') 18 ok,(word,flags)=oracle(word,pool[index],OPS[op]);assert ok 19 expected.append((word,flags)) 20 raw=''.join(rows).encode() 21 result=subprocess.run([str(reference)],input=raw,capture_output=True,check=True,timeout=60) 22 lines=result.stdout.splitlines();assert len(lines)==len(expected) 23 for line,(word,flags) in zip(lines,expected): 24 actual,raised=line.split();actual=int(actual,16);raised=int(raised) 25 assert raised==sum(int(f)<<i for i,f in enumerate(flags)) 26 if nan(word):assert nan(actual) and actual&QUIET 27 else:assert actual==word 28 return dict(checked_steps=len(expected),input_sha256=hashlib.sha256(raw).hexdigest(), 29 output_sha256=hashlib.sha256(result.stdout).hexdigest(), 30 reference_sha256=hashlib.sha256(Path(reference).read_bytes()).hexdigest()) 31 32 33 class ChainTests(unittest.TestCase): 34 def test_no_fused_or_reassociated_rounding(self): 35 row=(0x3ff0000000000000,[0x3ca0000000000000],[(0,0),(0,0)]) 36 self.assertEqual(chain_oracle(row),(0x3ff0000000000000,(False,False,False,False,True))) 37 # Two separate halfway additions round to one; a fused sum rounds up one ULP. 38 self.assertNotEqual(chain_oracle(row)[0],0x3ff0000000000001) 39 40 def test_accumulation_retains_prior_exceptions(self): 41 row=(0x7fefffffffffffff,[0x4000000000000000,0,INF],[(2,0),(2,1),(0,2)]) 42 self.assertEqual(chain_oracle(row),(QNAN,(True,False,True,False,True))) 43 row=(0x3ff0000000000000,[0,INF],[(3,0),(3,1)]) 44 self.assertEqual(chain_oracle(row),(QNAN,(True,True,False,False,False))) 45 row=(MIN_NORMAL,[0x3fefffffffffffff,0x4000000000000000],[(2,0),(2,1)]) 46 word,flags=chain_oracle(row) 47 self.assertGreater(word,MIN_NORMAL);self.assertEqual(flags,(False,False,False,True,True)) 48 49 def test_empty_chain_is_bit_exact(self): 50 for word in [SIGN,INF|1,SIGN|QNAN|123]: 51 self.assertEqual(chain_oracle((word,[],[])),(word,(False,)*5)) 52 53 def test_reused_and_separate_equal_pool_slots(self): 54 g=groups() 55 self.assertEqual([chain_oracle(r) for r in g['affine-reuse-16']], 56 [chain_oracle(r) for r in g['affine-unique-16']]) 57 self.assertEqual(g['affine-unique-16'][0][2],[(0 if j%2 else 2,j) for j in range(32)]) 58 59 def test_valid_domain_and_result_range(self): 60 for initial,pool,steps in cases(): 61 self.assertTrue(0<=initial<LIMIT) 62 self.assertTrue(all(0<=word<LIMIT for word in pool)) 63 self.assertTrue(all(0<=op<4 and 0<=index<len(pool) for op,index in steps)) 64 word,flags=chain_oracle((initial,pool,steps)) 65 self.assertTrue(0<=word<LIMIT);self.assertEqual(len(flags),5) 66 67 @unittest.skipUnless(os.environ.get('TRICU_FLOAT_HOST'),'set TRICU_FLOAT_HOST to the C fenv reference') 68 def test_steps_against_c_fenv(self): 69 report=host_trace(os.environ['TRICU_FLOAT_HOST']) 70 self.assertGreater(report['checked_steps'],3000) 71 72 73 if __name__=='__main__':unittest.main()