code wiki / (root) / nx_unitconv.nx

nx_unitconv.nx source

↩ module page · 206 lines · 9243 B

1// nx_unitconv.nx -- MEASUREMENT SYSTEMS + EXACT CONVERSION (engineering-twin metrology substrate). 2// (Placement note: DISTINCT from nx_units.nx = kernel_v2 SI dimensional-ANALYSIS engine (physics equations, 3// 7-D dim vectors) and nx_measure.nx = Lebesgue measure theory. THIS organ converts between the world's 4// still-used measurement SYSTEMS -- metric, US customary, UK imperial, survey -- on the runtime/i64 stack. 5// They compose, not compete: analysis checks equations, THIS checks the numbers feeding them.) 6// 7// The anti-Mars-Climate-Orbiter organ: that probe was lost because one team produced lbf-s and the other READ 8// it as N-s (factor 4.4482) -- a silent unit mislabel. Doctrine: (1) every value travels WITH a unit index, 9// (2) conversions are EXACT RATIONALS from the international definitions (1 in = 25.4 mm EXACT since 1959, 10// 1 lb = 453.59237 g EXACT), never floats, (3) every conversion returns an EXACTNESS REMAINDER (0 = exact) so 11// rounding is VISIBLE, (4) dimension mismatches and overflow REFUSE LOUDLY (error codes) -- never a wrong number. 12// Covers metric (SI), US customary, UK imperial (gallon/pint DIFFER from US -- still in daily use), the 13// US-survey-foot trap (1200/3937 m, 2ppm off the international foot -- state-plane data hazard), and the twin's 14// own fx256-mm grid as a first-class unit. Bases integer-exact: LENGTH nm, MASS ug, FORCE nN, VOLUME uL, 15// TIME us, IMPULSE nN-s. Data-driven registry: units are TABLE ROWS, auto-gcd-reduced + overflow-guarded at 16// add (fail-fast). license_tier: ORIGINAL 17import "nx_syscalls.nx" 18 19const UR_LEN: i64 = 1 20const UR_MASS: i64 = 2 21const UR_FORCE: i64 = 3 22const UR_VOL: i64 = 4 23const UR_TIME: i64 = 5 24const UR_IMP: i64 = 6 25const UR_BIG: i64 = 9000000000000000000 26 27func ur_gcd(a0: i64, b0: i64) -> i64 { 28 var a: i64 = a0 29 var b: i64 = b0 30 if a < 0 { a = 0 - a } 31 if b < 0 { b = 0 - b } 32 while b != 0 { 33 let t: i64 = a % b 34 a = b 35 b = t 36 } 37 if a == 0 { return 1 } 38 return a 39} 40 41// registry ctx u[]: [0]=count [1]=names(*i64) [2]=dims [3]=nums [4]=dens [5]=cap 42func ur_init(u: *i64, cap: i64) -> i64 { 43 u[0] = 0 44 u[1] = sys_mmap(cap * 8) as i64 45 u[2] = sys_mmap(cap * 8) as i64 46 u[3] = sys_mmap(cap * 8) as i64 47 u[4] = sys_mmap(cap * 8) as i64 48 u[5] = cap 49 return 0 50} 51// add unit: value_in_base = value * num/den. Auto-reduces; REFUSES (-1) if the reduced ratio would overflow the 52// exact conversion machinery (fail-fast at registration -- a refused unit can never silently mis-convert). 53func ur_add(u: *i64, name: *u8, dim: i64, num: i64, den: i64) -> i64 { 54 if num <= 0 { return 0 - 1 } 55 if den <= 0 { return 0 - 1 } 56 let g: i64 = ur_gcd(num, den) 57 let rn: i64 = num / g 58 let rd: i64 = den / g 59 if rn > UR_BIG / rd { return 0 - 1 } // division-form guard (guard itself cannot overflow) 60 let cnt: i64 = u[0] 61 if cnt >= u[5] { return 0 - 1 } 62 let names: *i64 = u[1] as *i64 63 let dims: *i64 = u[2] as *i64 64 let nums: *i64 = u[3] as *i64 65 let dens: *i64 = u[4] as *i64 66 names[cnt] = name as i64 67 dims[cnt] = dim 68 nums[cnt] = rn 69 dens[cnt] = rd 70 u[0] = cnt + 1 71 return cnt 72} 73func ur_streq(a: *u8, b: *u8) -> i64 { 74 var i: i64 = 0 75 var go: i64 = 1 76 while go == 1 { 77 if a[i] != b[i] { return 0 } 78 if a[i] == (0 as u8) { go = 0 } 79 i = i + 1 80 } 81 return 1 82} 83func ur_find(u: *i64, name: *u8) -> i64 { 84 let names: *i64 = u[1] as *i64 85 var i: i64 = 0 86 while i < u[0] { 87 if ur_streq(names[i] as *u8, name) == 1 { return i } 88 i = i + 1 89 } 90 return 0 - 1 91} 92 93// floor(val*num/den) for val,num,den>0 via split -- exact, overflow-REFUSING (-3). out2[0]=floor out2[1]=rem 94func ur_mdiv(val: i64, num: i64, den: i64, out2: *i64) -> i64 { 95 let q: i64 = val / den 96 let r: i64 = val % den 97 if q > 0 { if num > UR_BIG / q { return 0 - 3 } } 98 let hi: i64 = q * num 99 if r > 0 { if num > UR_BIG / r { return 0 - 3 } } 100 let lo: i64 = r * num 101 let add: i64 = lo / den 102 if hi > UR_BIG - add { return 0 - 3 } 103 out2[0] = hi + add 104 out2[1] = lo % den 105 return 0 106} 107 108// CONVERT val (integer count of unit ia) -> unit ib. out2[0]=ROUNDED result, out2[1]=pre-round remainder 109// (0 == EXACT). Returns 0 ok; -1 bad index; -2 DIMENSION MISMATCH (the loud anti-JPL refusal); -3 overflow. 110func ur_conv(u: *i64, val: i64, ia: i64, ib: i64, out2: *i64) -> i64 { 111 if ia < 0 { return 0 - 1 } 112 if ib < 0 { return 0 - 1 } 113 if ia >= u[0] { return 0 - 1 } 114 if ib >= u[0] { return 0 - 1 } 115 let dims: *i64 = u[2] as *i64 116 if dims[ia] != dims[ib] { return 0 - 2 } 117 let nums: *i64 = u[3] as *i64 118 let dens: *i64 = u[4] as *i64 119 var na: i64 = nums[ia] 120 var da: i64 = dens[ia] 121 var nb: i64 = nums[ib] 122 var db: i64 = dens[ib] 123 // combined factor = (na/da)*(db/nb); cross-reduce BEFORE multiplying 124 let g1: i64 = ur_gcd(na, nb) 125 na = na / g1 126 nb = nb / g1 127 let g2: i64 = ur_gcd(db, da) 128 db = db / g2 129 da = da / g2 130 if na > UR_BIG / db { return 0 - 3 } 131 let cn: i64 = na * db 132 if da > UR_BIG / nb { return 0 - 3 } 133 let cd: i64 = da * nb 134 let g3: i64 = ur_gcd(cn, cd) 135 let fnum: i64 = cn / g3 136 let fden: i64 = cd / g3 137 var av: i64 = val 138 var sign: i64 = 1 139 if av < 0 { sign = 0 - 1; av = 0 - av } 140 let rc: i64 = ur_mdiv(av, fnum, fden, out2) 141 if rc != 0 { return rc } 142 if 2 * out2[1] >= fden { out2[0] = out2[0] + 1 } // round-half-up; out2[1] stays as exactness marker 143 if sign < 0 { out2[0] = 0 - out2[0] } 144 return 0 145} 146 147// the standard registry: metric + US customary + UK imperial + survey-foot + the twin's fx256 grid. 148// Every factor is EXACT from the international definitions (1959 yard&pound agreement; UK gallon 1985). 149func ur_std(u: *i64) -> i64 { 150 // LENGTH, base nanometre 151 ur_add(u, "nm" as *u8, UR_LEN, 1, 1) 152 ur_add(u, "um" as *u8, UR_LEN, 1000, 1) 153 ur_add(u, "mm" as *u8, UR_LEN, 1000000, 1) 154 ur_add(u, "cm" as *u8, UR_LEN, 10000000, 1) 155 ur_add(u, "m" as *u8, UR_LEN, 1000000000, 1) 156 ur_add(u, "km" as *u8, UR_LEN, 1000000000000, 1) 157 ur_add(u, "uin" as *u8, UR_LEN, 127, 5) // microinch = 25.4 nm 158 ur_add(u, "thou" as *u8, UR_LEN, 25400, 1) // mil = 25.4 um 159 ur_add(u, "in" as *u8, UR_LEN, 25400000, 1) // 25.4 mm EXACT (1959) 160 ur_add(u, "ft" as *u8, UR_LEN, 304800000, 1) // international foot 161 ur_add(u, "yd" as *u8, UR_LEN, 914400000, 1) 162 ur_add(u, "mile" as *u8, UR_LEN, 1609344000000, 1) 163 ur_add(u, "ftUSsurvey" as *u8, UR_LEN, 1200000000000, 3937) // 1200/3937 m EXACT (the 2ppm trap) 164 ur_add(u, "fx256mm" as *u8, UR_LEN, 15625, 4) // the twin grid: 1/256 mm = 3906.25 nm 165 // MASS, base microgram 166 ur_add(u, "ug" as *u8, UR_MASS, 1, 1) 167 ur_add(u, "mg" as *u8, UR_MASS, 1000, 1) 168 ur_add(u, "g" as *u8, UR_MASS, 1000000, 1) 169 ur_add(u, "kg" as *u8, UR_MASS, 1000000000, 1) 170 ur_add(u, "tonne" as *u8, UR_MASS, 1000000000000, 1) 171 ur_add(u, "oz" as *u8, UR_MASS, 226796185, 8) // 28.349523125 g EXACT 172 ur_add(u, "lb" as *u8, UR_MASS, 453592370, 1) // 453.59237 g EXACT (1959) 173 ur_add(u, "stone" as *u8, UR_MASS, 6350293180, 1) // 14 lb (UK) 174 ur_add(u, "tonUS" as *u8, UR_MASS, 907184740000, 1) // 2000 lb short ton 175 ur_add(u, "tonUK" as *u8, UR_MASS, 1016046908800, 1) // 2240 lb long ton 176 // FORCE, base nanonewton 177 ur_add(u, "nN" as *u8, UR_FORCE, 1, 1) 178 ur_add(u, "N" as *u8, UR_FORCE, 1000000000, 1) 179 ur_add(u, "kN" as *u8, UR_FORCE, 1000000000000, 1) 180 ur_add(u, "kgf" as *u8, UR_FORCE, 9806650000, 1) // 9.80665 N EXACT (standard gravity) 181 ur_add(u, "lbf" as *u8, UR_FORCE, 8896443230521, 2000) // 4.4482216152605 N EXACT = 0.45359237*9.80665 182 // VOLUME, base microlitre 183 ur_add(u, "uL" as *u8, UR_VOL, 1, 1) 184 ur_add(u, "mL" as *u8, UR_VOL, 1000, 1) 185 ur_add(u, "L" as *u8, UR_VOL, 1000000, 1) 186 ur_add(u, "m3" as *u8, UR_VOL, 1000000000000000, 1) 187 ur_add(u, "galUS" as *u8, UR_VOL, 473176473, 125) // 3.785411784 L EXACT 188 ur_add(u, "galUK" as *u8, UR_VOL, 4546090, 1) // 4.54609 L EXACT (differs from US!) 189 ur_add(u, "pintUS" as *u8, UR_VOL, 473176473, 1000) 190 ur_add(u, "pintUK" as *u8, UR_VOL, 2273045, 4) 191 ur_add(u, "flozUS" as *u8, UR_VOL, 473176473, 16000) 192 ur_add(u, "flozUK" as *u8, UR_VOL, 454609, 16) 193 // TIME, base microsecond 194 ur_add(u, "us" as *u8, UR_TIME, 1, 1) 195 ur_add(u, "ms" as *u8, UR_TIME, 1000, 1) 196 ur_add(u, "s" as *u8, UR_TIME, 1000000, 1) 197 ur_add(u, "min" as *u8, UR_TIME, 60000000, 1) 198 ur_add(u, "hr" as *u8, UR_TIME, 3600000000, 1) 199 ur_add(u, "day" as *u8, UR_TIME, 86400000000, 1) 200 // IMPULSE, base nanonewton-second (the Mars Climate Orbiter dimension) 201 ur_add(u, "nNs" as *u8, UR_IMP, 1, 1) 202 ur_add(u, "Ns" as *u8, UR_IMP, 1000000000, 1) 203 ur_add(u, "lbfs" as *u8, UR_IMP, 8896443230521, 2000) // the mislabel that killed the orbiter 204 ur_add(u, "kgfs" as *u8, UR_IMP, 9806650000, 1) 205 return u[0] 206}