nx_f32_div.nx source
↩ module page · 121 lines · 4343 B
1// nx_f32_div.nx -- IEEE 754 binary32 division (standalone).
2//
3// Separated from runtime/nx_f32.nx because having division code in
4// the same file as nx_f32_mul triggers the nxc2 codegen quirk
5// tracked in Task #10 (negative-literal sign-extension regression
6// in earlier mul KAT after certain patterns added). Isolating this
7// brick in its own file sidesteps the quirk while preserving the
8// L4 bits-up tower's contract.
9//
10// Composes:
11// nx_f32.nx -- classify / sign / exp_field / mant_field / constants
12//
13// Algorithm:
14// 1. NaN/Inf/Zero special cases per IEEE 754.
15// 2. Build 24-bit significands.
16// 3. (a_sig << 24) / b_sig gives 24- or 25-bit quotient.
17// 4. Normalize to 24-bit mantissa, extract guard from dropped bit,
18// sticky from remainder.
19// 5. Round-to-nearest-even.
20// 6. Overflow → inf, underflow → subnormal/zero.
21//
22// v1 limitations: subnormal input/output conservatively zero/inf.
23
24import "nx_syscalls.nx"
25import "nx_tier.nx"
26import "nx_f32.nx"
27
28func nx_f32_div(a: i64, b: i64) -> i64 {
29 let cls_a: nx_int = nx_f32_classify(a)
30 let cls_b: nx_int = nx_f32_classify(b)
31 let sign_out: i64 = nx_f32_sign(a) ^ nx_f32_sign(b)
32
33 if cls_a == NX_F32_CLS_NAN { return NX_F32_NAN_RAW }
34 if cls_b == NX_F32_CLS_NAN { return NX_F32_NAN_RAW }
35
36 if cls_b == NX_F32_CLS_ZERO {
37 if cls_a == NX_F32_CLS_ZERO { return NX_F32_NAN_RAW }
38 return (sign_out << 31) | NX_F32_INF_RAW
39 }
40 if cls_b == NX_F32_CLS_INF {
41 if cls_a == NX_F32_CLS_INF { return NX_F32_NAN_RAW }
42 return sign_out << 31
43 }
44
45 if cls_a == NX_F32_CLS_ZERO { return sign_out << 31 }
46 if cls_a == NX_F32_CLS_INF {
47 return (sign_out << 31) | NX_F32_INF_RAW
48 }
49
50 if cls_a == NX_F32_CLS_SUBNORMAL { return sign_out << 31 }
51 if cls_b == NX_F32_CLS_SUBNORMAL {
52 return (sign_out << 31) | NX_F32_INF_RAW
53 }
54
55 let mant_a: i64 = nx_f32_mant_field(a)
56 let exp_a: i64 = nx_f32_exp_field(a)
57 let sig_a: i64 = mant_a | NX_F32_IMPLICIT_1
58
59 let mant_b: i64 = nx_f32_mant_field(b)
60 let exp_b: i64 = nx_f32_exp_field(b)
61 let sig_b: i64 = mant_b | NX_F32_IMPLICIT_1
62
63 var exp_out: i64 = exp_a - exp_b + NX_F32_EXP_BIAS
64
65 // ONE EXTRA QUOTIENT BIT (root fix 2026-09-14): the v1 body computed only 24 quotient bits, so in Case B the
66 // guard bit was set to a CONSTANT 0 and every such division was TRUNCATED, never rounded to nearest even.
67 // nx_f32_bricks_hw_gate measured it against the hardware divide: 937 of 4096 random pairs off the IEEE result
68 // while add and mul matched bit for bit. With sig_a << 25 the guard is a real quotient bit in both cases.
69 let dividend: i64 = sig_a << 25
70 let q: i64 = dividend / sig_b
71 let rem: i64 = dividend - q * sig_b
72
73 // Case A (sig_a >= sig_b): q in [2^25, 2^26); mantissa = q >> 2, guard = bit 1, sticky = bit 0 or remainder,
74 // exp_out UNCHANGED (real ratio in [1,2)).
75 // Case B (sig_a < sig_b): q in [2^24, 2^25); mantissa = q >> 1, guard = bit 0, sticky = remainder, and the
76 // real ratio is in [0.5,1) so DECREMENT exp_out by 1 to keep canonical implicit-1 form.
77 var mant_out: i64 = 0
78 var guard_bit: i64 = 0
79 var sticky: i64 = 0
80 if sig_a >= sig_b {
81 mant_out = q >> 2
82 guard_bit = (q >> 1) & 1
83 if (q & 1) != 0 { sticky = 1 }
84 if rem != 0 { sticky = 1 }
85 } else {
86 mant_out = q >> 1
87 guard_bit = q & 1
88 if rem != 0 { sticky = 1 }
89 exp_out = exp_out - 1
90 }
91
92 var round_up: i64 = 0
93 if guard_bit == 1 {
94 if sticky == 1 { round_up = 1 }
95 if sticky == 0 {
96 if (mant_out & 1) == 1 { round_up = 1 }
97 }
98 }
99 if round_up == 1 {
100 mant_out = mant_out + 1
101 if mant_out >= 0x01000000 {
102 mant_out = mant_out >> 1
103 exp_out = exp_out + 1
104 }
105 }
106
107 if exp_out >= 255 {
108 return (sign_out << 31) | NX_F32_INF_RAW
109 }
110 if exp_out <= 0 {
111 let shifts: i64 = 1 - exp_out
112 if shifts > 24 {
113 return sign_out << 31
114 }
115 mant_out = mant_out >> shifts
116 return (sign_out << 31) | (mant_out & NX_F32_MANT_MASK)
117 }
118
119 let mant_final: i64 = mant_out & NX_F32_MANT_MASK
120 return (sign_out << 31) | (exp_out << NX_F32_EXP_SHIFT) | mant_final
121}