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}