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£ 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}