code wiki / (root) / nx_pow10_test.nx

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}