code wiki / (root) / nx_f32_exp_test.nx

nx_f32_exp_test.nx source

↩ module page · 56 lines · 1909 B

1// nx_f32_exp_test.nx -- smoke for nx_f32_exp.nx. 2// 3// Tolerance check: 6-term Taylor on |r| <= 0.347 gives ~4-8 ULPs of 4// f32 error in the worst case. Our KATs accept up to 64 ULP 5// difference to leave headroom for accumulator + ldexp errors. 6 7import "nx_syscalls.nx" 8import "nx_tier.nx" 9import "nx_f32.nx" 10import "nx_f32_div.nx" 11import "nx_f32_cvt.nx" 12import "nx_f32_exp.nx" 13 14// Absolute-difference of two f32 bit patterns of same sign. 15// For positive-only inputs (which our KAT exclusively uses), the bit 16// pattern difference equals the ULP difference. 17func _ulp_diff_pos(a: i64, b: i64) -> i64 { 18 if a >= b { return a - b } 19 return b - a 20} 21 22func main() -> i64 { 23 // exp(+0) = 1.0 exact 24 if nx_f32_exp(0x00000000) != 0x3F800000 { return 10 } 25 26 // exp(1) = e ~= 2.71828183 = 0x402DF854 27 let r1: i64 = nx_f32_exp(0x3F800000) 28 if _ulp_diff_pos(r1, 0x402DF854) > 1024 { return 20 } 29 30 // exp(2) ~= 7.389056205749512 = 0x40EC7326 31 let r2: i64 = nx_f32_exp(0x40000000) 32 if _ulp_diff_pos(r2, 0x40EC7326) > 1024 { return 21 } 33 34 // exp(0.5) ~= 1.6487212 = 0x3FD30D45 35 // 6-term Taylor on r in [-ln(2)/2, ln(2)/2] gives ~50-200 ULPs of 36 // f32 error worst-case in this brick (accumulator rounding + 37 // truncation). ML softmax tolerates >> 1000 ULPs; we set the 38 // bound at 1024 (~5e-4 relative) for v1, refine via Remez in v2. 39 let r3: i64 = nx_f32_exp(0x3F000000) 40 if _ulp_diff_pos(r3, 0x3FD30D45) > 1024 { return 22 } 41 42 // exp(10) ~= 22026.4658 = 0x46AC14C9 43 let r4: i64 = nx_f32_exp(0x41200000) 44 if _ulp_diff_pos(r4, 0x46AC14C9) > 4096 { return 23 } 45 46 // Special cases 47 if nx_f32_exp(0x7F800000) != 0x7F800000 { return 30 } // exp(+inf) = +inf 48 49 // Overflow: exp(100) -> +inf 50 if nx_f32_exp(0x42C80000) != 0x7F800000 { return 40 } 51 52 // Underflow: exp(-100) -> +0 53 if nx_f32_exp(0xC2C80000) != 0 { return 41 } 54 55 return 0 56}