nx_pow10_test.nx source
↩ module page · 201 lines · 9845 B
1// nx_pow10_test.nx -- the gate the lane's most load-bearing primitive never had.
2//
3// nx_pow10 sits underneath peptide ionisation (pI), thermal death kinetics
4// (D/z lethality), shelf life, and stability -- four organs whose numbers are
5// only as good as this table. It was tested transitively by all of them and
6// directly by none, which means an error here would have been attributed to
7// whichever caller noticed it first.
8//
9// ===== HOW YOU LIAR-KILL A MATH PRIMITIVE WITH NO ORACLE ==========
10//
11// There is no float reference available to check against, and hand-typed
12// "expected" values would just be a second copy of the same table. So the
13// oracle is the function's OWN FUNCTIONAL EQUATION:
14//
15// 10^a * 10^b = 10^(a+b)
16//
17// Nothing about the table or the interpolation is assumed; the identity has
18// to hold, and the amount by which it fails IS the interpolation error,
19// measured rather than quoted. T5 scans the whole decade for the worst case
20// instead of sampling a point that flatters the implementation.
21// expect_exit: 0 license_tier: ORIGINAL
22
23import "nx_syscalls.nx"
24import "nx_pow10.nx"
25
26func t_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
27func t_putn(v: i64) -> i64 { let bb: *u8 = sys_mmap(28); var m: i64 = v; if m < 0 { m = 0 - m; sys_write(1, "-" as *u8, 1) } let t: *u8 = sys_mmap(28); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 } sys_write(1, bb, k); return 0 }
28
29func iabs(v: i64) -> i64 { if v < 0 { return 0 - v } return v }
30
31func main() -> i64 {
32 var pass: i64 = 0
33 var total: i64 = 0
34
35 // --- T1 EXACT at every decade boundary. Whole decades are repeated
36 // integer multiplication, so any drift here is a coding error,
37 // not an approximation, and the assertion is exact. ---
38 total = total + 1
39 var ok1: i64 = 1
40 if ipow10_q3(0) != 1000 { ok1 = 0 }
41 if ipow10_q3(1000) != 10000 { ok1 = 0 }
42 if ipow10_q3(2000) != 100000 { ok1 = 0 }
43 if ipow10_q3(3000) != 1000000 { ok1 = 0 }
44 if ipow10_q3(0 - 1000) != 100 { ok1 = 0 }
45 if ipow10_q3(0 - 2000) != 10 { ok1 = 0 }
46 t_puts("T1 decade anchors 10^0/1/2/3/-1/-2 exact: " as *u8)
47 if ok1 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
48
49 // --- T2 PUBLISHED ANCHORS. log10(2)=0.30103, log10(3)=0.47712,
50 // log10(5)=0.69897 are values nobody needs this table to know.
51 // Feeding them back must return 2.000, 3.000, 5.000. ---
52 total = total + 1
53 let p2: i64 = ipow10_q3(301)
54 let p3: i64 = ipow10_q3(477)
55 let p5: i64 = ipow10_q3(699)
56 t_puts("T2 10^0.301=" as *u8); t_putn(p2); t_puts(" (2000) 10^0.477=" as *u8); t_putn(p3)
57 t_puts(" (3000) 10^0.699=" as *u8); t_putn(p5); t_puts(" (5000), each within 1%: " as *u8)
58 var ok2: i64 = 1
59 if iabs(p2 - 2000) > 20 { ok2 = 0 }
60 if iabs(p3 - 3000) > 30 { ok2 = 0 }
61 if iabs(p5 - 5000) > 50 { ok2 = 0 }
62 if ok2 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
63
64 // --- T3 ROUND TRIP: log10 is the table run backwards, so it must undo
65 // ipow10 exactly at points both paths can represent. ---
66 total = total + 1
67 var ok3: i64 = 1
68 var d3: i64 = 0
69 var worst_rt: i64 = 0
70 while d3 <= 900 {
71 let back: i64 = ilog10_milli(ipow10_q3(d3))
72 if iabs(back - d3) > worst_rt { worst_rt = iabs(back - d3) }
73 d3 = d3 + 50
74 }
75 if worst_rt > 2 { ok3 = 0 }
76 t_puts("T3 round trip log10(10^x)==x over 0..0.9, worst drift=" as *u8); t_putn(worst_rt)
77 t_puts(" milli (<=2): " as *u8)
78 if ok3 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
79
80 // --- T4 MONOTONE. A rate/lethality/shelf-life model that inverts
81 // anywhere would be catastrophic and silent. Checked across the
82 // whole interpolated decade, not at sampled points. ---
83 total = total + 1
84 var ok4: i64 = 1
85 var d4: i64 = 1
86 while d4 <= 1000 {
87 if ipow10_q3(d4) < ipow10_q3(d4 - 1) { ok4 = 0 }
88 d4 = d4 + 1
89 }
90 t_puts("T4 strictly non-decreasing across all 1000 milli-steps of a decade: " as *u8)
91 if ok4 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
92
93 // --- T5 ***THE MEASUREMENT*** Functional equation 10^d * 10^d = 10^2d.
94 // Scans every interpolated point for the WORST discrepancy. At
95 // the doubled point the table entry is near-exact, so the gap is
96 // about twice the single-evaluation interpolation error. ---
97 total = total + 1
98 var worst: i64 = 0
99 var worst_at: i64 = 0
100 var d5: i64 = 1
101 while d5 < 500 {
102 let sq: i64 = ipow10_q3(d5) * ipow10_q3(d5) / 1000
103 let direct: i64 = ipow10_q3(d5 * 2)
104 let rel: i64 = iabs(sq - direct) * 1000 / direct
105 if rel > worst { worst = rel; worst_at = d5 }
106 d5 = d5 + 1
107 }
108 t_puts("T5 worst |10^d*10^d - 10^2d| = " as *u8); t_putn(worst)
109 t_puts(" permil at d=" as *u8); t_putn(worst_at)
110 t_puts(" => single-eval chord error ~" as *u8); t_putn(worst / 2); t_puts(" permil: " as *u8)
111 var ok5: i64 = 1
112 // Upper bound: the identity must hold to better than 2%.
113 if worst > 20 { ok5 = 0 }
114 // NON-VACUITY: the scan must actually FIND the error. If this came back
115 // near zero the test would be measuring nothing and silently passing.
116 if worst < 5 { ok5 = 0 }
117 if ok5 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
118
119 // --- T6 THE SIGN OF THE ERROR IS THE DOCUMENTED ONE. 10^x is convex,
120 // so a chord lies ABOVE it and interpolated values must never
121 // come back low. Callers depend on this direction: a negative
122 // exponent then under-states, which is the safe way to be wrong
123 // in a lethality or potency calculation.
124 //
125 // ⚠THE ORACLE HAS TO BE A TABLE ENTRY. Comparing an interpolated
126 // value against another interpolated value measures the DIFFERENCE
127 // of two errors and says nothing about either one's sign -- the
128 // first cut of this test did exactly that and reported 35 bogus
129 // violations. Stepping d by 50 puts 2d on a multiple of 100,
130 // i.e. a real table entry, which is the only near-exact reference
131 // available without importing a float. The +/-1 slack absorbs
132 // those entries' own round-to-nearest. ---
133 total = total + 1
134 var ok6: i64 = 1
135 var d6: i64 = 50
136 var below: i64 = 0
137 var checked: i64 = 0
138 while d6 <= 450 {
139 let sq6: i64 = ipow10_q3(d6) * ipow10_q3(d6) / 1000
140 if sq6 < ipow10_q3(d6 * 2) - 1 { below = below + 1 }
141 checked = checked + 1
142 d6 = d6 + 50
143 }
144 if below > 0 { ok6 = 0 }
145 if checked < 9 { ok6 = 0 }
146 t_puts("T6 chord never falls below the curve at table-entry oracles (" as *u8); t_putn(below)
147 t_puts(" violations of " as *u8); t_putn(checked); t_puts("): " as *u8)
148 if ok6 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
149
150 // --- T7 FAIL-CLOSED. log10 of zero or a negative is undefined, and
151 // returning 0 would be a lie that propagates into a divisor. ---
152 total = total + 1
153 var ok7: i64 = 1
154 if ilog10_milli(0) != IP10_INVALID { ok7 = 0 }
155 if ilog10_milli(0 - 500) != IP10_INVALID { ok7 = 0 }
156 t_puts("T7 log10(0) and log10(negative) REFUSED (not 0): " as *u8)
157 if ok7 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
158
159 // --- T8 CLAMP, and it SATURATES rather than wrapping. Beyond +/-6
160 // decades the caller is outside any physical range this serves. ---
161 total = total + 1
162 var ok8: i64 = 1
163 if ipow10_q3(6000) != ipow10_q3(9000) { ok8 = 0 }
164 if ipow10_q3(0 - 6000) != ipow10_q3(0 - 9000) { ok8 = 0 }
165 if ipow10_q3(6000) <= 0 { ok8 = 0 }
166 t_puts("T8 saturates at +/-6 decades without wrapping: " as *u8)
167 if ok8 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
168
169 // --- T9 The mantissa table's own end conditions: entry 0 is 1.0 and
170 // entry 10 is the next decade, which is what makes the top
171 // bracket interpolable at all. ---
172 total = total + 1
173 var ok9: i64 = 1
174 if ipow10_mantissa_q3(0) != 1000 { ok9 = 0 }
175 if ipow10_mantissa_q3(10) != 10000 { ok9 = 0 }
176 var d9: i64 = 0
177 while d9 < 10 {
178 if ipow10_mantissa_q3(d9 + 1) <= ipow10_mantissa_q3(d9) { ok9 = 0 }
179 d9 = d9 + 1
180 }
181 t_puts("T9 mantissa table closed at both ends and strictly increasing: " as *u8)
182 if ok9 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
183
184 // --- T10 NEGATIVE EXPONENTS GO THROUGH DIVISION, per the law banked in
185 // nx_thermal_process: in fixed point, divide by the large number,
186 // never materialise a small one. 10^-x * 10^x must return to 1. ---
187 total = total + 1
188 var ok10: i64 = 1
189 var d10: i64 = 100
190 while d10 <= 900 {
191 let prod: i64 = ipow10_q3(d10) * ipow10_q3(0 - d10) / 1000
192 if iabs(prod - 1000) > 20 { ok10 = 0 }
193 d10 = d10 + 100
194 }
195 t_puts("T10 10^-x * 10^x returns to 1.000 within 2% across the decade: " as *u8)
196 if ok10 == 1 { pass = pass + 1; t_puts("PASS\n" as *u8) } else { t_puts("FAIL\n" as *u8) }
197
198 t_puts("POW10-GATE passed " as *u8); t_putn(pass); t_puts("/" as *u8); t_putn(total)
199 if pass == total { t_puts(" verdict=GREEN\n" as *u8); sys_exit(0); return 0 }
200 t_puts(" verdict=RED\n" as *u8); sys_exit(1); return 1
201}