code wiki / (root) / nx_essentials.nx

nx_essentials.nx source

↩ module page · 408 lines · 11219 B

1// nx_essentials.nx -- the gajillion tiny reused functions. 2// 3// User cardinal 2026-05-15: "I'm sure there's a gajillion basic or 4// tiny functions that could live closer to the bits faster and 5// better, that are reused over and over again that means they really 6// should probably just be primitives." 7// 8// This file is the substrate's batch of MICRO-PRIMITIVES every 9// program in the substrate reuses. Built closer to the bits so 10// callers stop reimplementing them per-file. ~25 functions across 11// 7 families: 12// 13// 1. Comparison / clamp min / max / clamp / signed-clamp 14// 2. Sign / abs / swap abs / sign / swap_via_pointer 15// 3. Saturating arithmetic sat_add / sat_sub / sat_mul 16// 4. Q10 fixed-point helpers q10_mul / q10_div / lerp / pct 17// 5. Integer number theory gcd / lcm / pow_int / mod_floor 18// 6. Bit-level popcount / clz / ctz / log2_floor / 19// log2_ceil / bit_test / set / clear / 20// rotate_left / rotate_right 21// 7. Byte / char essentials memcpy / memset / memcmp / char_is_* 22// / to_lower / to_upper 23// 24// Every function: pure, deterministic, nx_int / *u8. No allocations. 25// No fidelity reporting (these are too small to warrant dual-reading; 26// they ARE the bits-up substrate that the dual-reading primitives use). 27// 28// genealogy_id: warren_2013_hackers_delight + knuth_taocp_v2_seminum + 29// c_standard_iso_9899 + python_stdlib_2025 30// lineage_id: essentials_micro_primitives_q10 31 32// nx_safety_envelope: 33// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 34// sil_target: SIL1 35// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_tier.nx" 40import "nx_bits.nx" 41 42const NX_ESS_Q: nx_int = 1024 43const NX_ESS_I64_MAX: nx_int = 9223372036854775807 44const NX_ESS_I64_MIN: nx_int = -9223372036854775807 45 46// ===== Family 1: comparison / clamp ================================== 47 48func nx_min2(a: nx_int, b: nx_int) -> nx_int { 49 if a < b { return a } 50 return b 51} 52 53func nx_max2(a: nx_int, b: nx_int) -> nx_int { 54 if a > b { return a } 55 return b 56} 57 58func nx_min3(a: nx_int, b: nx_int, c: nx_int) -> nx_int { 59 var m: nx_int = a 60 if b < m { m = b } 61 if c < m { m = c } 62 return m 63} 64 65func nx_max3(a: nx_int, b: nx_int, c: nx_int) -> nx_int { 66 var m: nx_int = a 67 if b > m { m = b } 68 if c > m { m = c } 69 return m 70} 71 72// Clamp v into [lo, hi]. 73func nx_clamp(v: nx_int, lo: nx_int, hi: nx_int) -> nx_int { 74 if v < lo { return lo } 75 if v > hi { return hi } 76 return v 77} 78 79// Q10-specific clamps -- the most common case. 80func nx_clamp_q10(v: nx_int) -> nx_int { 81 if v < 0 { return 0 } 82 if v > NX_ESS_Q { return NX_ESS_Q } 83 return v 84} 85 86func nx_clamp_signed_q10(v: nx_int) -> nx_int { 87 if v < -NX_ESS_Q { return -NX_ESS_Q } 88 if v > NX_ESS_Q { return NX_ESS_Q } 89 return v 90} 91 92// ===== Family 2: sign / abs / swap =================================== 93 94func nx_abs(v: nx_int) -> nx_int { 95 if v < 0 { return -v } 96 return v 97} 98 99// -1 / 0 / +1. 100func nx_sign(v: nx_int) -> nx_int { 101 if v > 0 { return 1 } 102 if v < 0 { return -1 } 103 return 0 104} 105 106// In-place swap via pointer. Substrate doesn't have references so 107// caller passes pointers to the two cells. 108func nx_swap_via_ptr(a: *nx_int, b: *nx_int) -> nx_int { 109 let tmp: nx_int = a[0] 110 a[0] = b[0] 111 b[0] = tmp 112 return 0 113} 114 115// ===== Family 3: saturating arithmetic =============================== 116// 117// Returns I64_MAX on positive overflow, I64_MIN on negative overflow. 118// Useful for substrate code that must never wrap around (Q10 math, 119// counters, sentinels). 120 121func nx_sat_add(a: nx_int, b: nx_int) -> nx_int { 122 if b > 0 { 123 if a > NX_ESS_I64_MAX - b { return NX_ESS_I64_MAX } 124 } 125 if b < 0 { 126 if a < NX_ESS_I64_MIN - b { return NX_ESS_I64_MIN } 127 } 128 return a + b 129} 130 131func nx_sat_sub(a: nx_int, b: nx_int) -> nx_int { 132 if b < 0 { 133 if a > NX_ESS_I64_MAX + b { return NX_ESS_I64_MAX } 134 } 135 if b > 0 { 136 if a < NX_ESS_I64_MIN + b { return NX_ESS_I64_MIN } 137 } 138 return a - b 139} 140 141func nx_sat_mul(a: nx_int, b: nx_int) -> nx_int { 142 if a == 0 { return 0 } 143 if b == 0 { return 0 } 144 let absA: nx_int = nx_abs(a) 145 let absB: nx_int = nx_abs(b) 146 // Check overflow: |a|*|b| > I64_MAX iff |a| > I64_MAX / |b|. 147 if absA > NX_ESS_I64_MAX / absB { 148 // Sign-preserving saturation. 149 if nx_sign(a) == nx_sign(b) { return NX_ESS_I64_MAX } 150 return NX_ESS_I64_MIN 151 } 152 return a * b 153} 154 155// ===== Family 4: Q10 fixed-point helpers ============================= 156 157// a * b in Q10: (a * b) / Q with overflow-aware multiply. 158func nx_q10_mul(a: nx_int, b: nx_int) -> nx_int { 159 return (a * b) / NX_ESS_Q 160} 161 162// a / b in Q10: (a * Q) / b. 163func nx_q10_div(a: nx_int, b: nx_int) -> nx_int { 164 if b == 0 { return 0 } 165 return (a * NX_ESS_Q) / b 166} 167 168// Linear interpolation in Q10: a + (b - a) * t / Q. 169func nx_lerp_q10(a: nx_int, b: nx_int, t_q10: nx_int) -> nx_int { 170 return a + ((b - a) * t_q10) / NX_ESS_Q 171} 172 173// Convert raw integer 0..100 to Q10. 174func nx_pct_to_q10(pct: nx_int) -> nx_int { 175 if pct <= 0 { return 0 } 176 if pct >= 100 { return NX_ESS_Q } 177 return (pct * NX_ESS_Q) / 100 178} 179 180// Convert Q10 to percent (rounds toward zero). 181func nx_q10_to_pct(q10: nx_int) -> nx_int { 182 return (q10 * 100) / NX_ESS_Q 183} 184 185// ===== Family 5: integer number theory =============================== 186 187// Euclidean GCD; handles negative inputs by abs-ing them first. 188func nx_gcd(a: nx_int, b: nx_int) -> nx_int { 189 var x: nx_int = nx_abs(a) 190 var y: nx_int = nx_abs(b) 191 while y != 0 { 192 let r: nx_int = x - (x / y) * y 193 x = y 194 y = r 195 } 196 return x 197} 198 199// LCM via standard identity. 200func nx_lcm(a: nx_int, b: nx_int) -> nx_int { 201 if a == 0 { return 0 } 202 if b == 0 { return 0 } 203 let g: nx_int = nx_gcd(a, b) 204 return nx_abs(a / g * b) 205} 206 207// Integer power: base^exp. exp must be >= 0; exp == 0 returns 1. 208func nx_pow_int(base: nx_int, exp: nx_int) -> nx_int { 209 if exp < 0 { return 0 } 210 if exp == 0 { return 1 } 211 var result: nx_int = 1 212 var b: nx_int = base 213 var e: nx_int = exp 214 while e > 0 { 215 let bit: nx_int = e - (e / 2) * 2 216 if bit == 1 { result = result * b } 217 b = b * b 218 e = e / 2 219 } 220 return result 221} 222 223// Modulo with FLOOR semantics (handles negative dividend correctly). 224// nx_mod_floor(-5, 3) = 1 (not -2 like trunc-toward-zero) 225func nx_mod_floor(a: nx_int, m: nx_int) -> nx_int { 226 if m == 0 { return 0 } 227 let r: nx_int = a - (a / m) * m 228 if r < 0 { 229 if m > 0 { return r + m } 230 return r - m 231 } 232 return r 233} 234 235// ===== Family 6: bit-level ============================================ 236 237// Hamming weight: count of set bits in a 64-bit word. 238// Branchless via SWAR (parallel summation tree). Uses standard 239// magic constants (Knuth/Warren). 240// Delegated to nx_bits_popcount64 for substrate-wide consolidation. 241// The intrinsic dispatch gives a measured 2x speedup over this SWAR 242// pattern. Original division-based phrasing preserved in the test 243// oracle for cross-target equivalence. 244func nx_popcount(x: nx_int) -> nx_int { 245 return nx_bits_popcount64(x) 246} 247 248// Delegated to nx_bits_clz64 (composes two intrinsic clz32 calls; 249// ~3 ops vs the legacy O(64) division loop). clz(0) = 64. 250func nx_clz(x: nx_int) -> nx_int { 251 return nx_bits_clz64(x) 252} 253 254// Delegated to nx_bits_ctz64 (composes two intrinsic ctz32 calls). 255// ctz(0) = 64. 256func nx_ctz(x: nx_int) -> nx_int { 257 return nx_bits_ctz64(x) 258} 259 260// floor(log2(n)) for n > 0; -1 if n <= 0. 261func nx_log2_floor(n: nx_int) -> nx_int { 262 if n <= 0 { return -1 } 263 var k: nx_int = 0 264 var v: nx_int = n 265 while v > 1 { 266 v = v / 2 267 k = k + 1 268 } 269 return k 270} 271 272// ceil(log2(n)) for n > 0; -1 if n <= 0. 273func nx_log2_ceil(n: nx_int) -> nx_int { 274 if n <= 0 { return -1 } 275 if n == 1 { return 0 } 276 let f: nx_int = nx_log2_floor(n) 277 let pow: nx_int = nx_pow_int(2, f) 278 if pow == n { return f } 279 return f + 1 280} 281 282// Test bit at position p (0-indexed from LSB). Returns 1 if set, 0 if clear. 283func nx_bit_test(x: nx_int, p: nx_int) -> nx_int { 284 if p < 0 { return 0 } 285 if p >= 64 { return 0 } 286 let mask: nx_int = nx_pow_int(2, p) 287 let v: nx_int = (x / mask) - ((x / mask) / 2) * 2 288 return v 289} 290 291// Set bit at position p; returns the modified word. 292func nx_bit_set(x: nx_int, p: nx_int) -> nx_int { 293 if p < 0 { return x } 294 if p >= 64 { return x } 295 let mask: nx_int = nx_pow_int(2, p) 296 return x | mask 297} 298 299// Clear bit at position p; returns the modified word. 300func nx_bit_clear(x: nx_int, p: nx_int) -> nx_int { 301 if p < 0 { return x } 302 if p >= 64 { return x } 303 let mask: nx_int = nx_pow_int(2, p) 304 return x & (~mask) 305} 306 307// ===== Family 7: byte / char essentials =============================== 308 309// memcpy: copy n bytes from src to dst. 310func nx_memcpy(dst: *u8, src: *u8, n: nx_int) -> nx_int { 311 var i: nx_int = 0 312 while i < n { 313 dst[i] = src[i] 314 i = i + 1 315 } 316 return 0 317} 318 319// memset: fill n bytes of buf with value v. 320func nx_memset(buf: *u8, v: u8, n: nx_int) -> nx_int { 321 var i: nx_int = 0 322 while i < n { 323 buf[i] = v 324 i = i + 1 325 } 326 return 0 327} 328 329// memcmp: returns 0 if equal, -1 if a < b lex order, +1 if a > b. 330func nx_memcmp(a: *u8, b: *u8, n: nx_int) -> nx_int { 331 var i: nx_int = 0 332 while i < n { 333 if a[i] != b[i] { 334 if a[i] < b[i] { return -1 } 335 return 1 336 } 337 i = i + 1 338 } 339 return 0 340} 341 342// Character classification (ASCII). 343func nx_char_is_digit(c: u8) -> nx_int { 344 if c >= 48 { 345 if c <= 57 { return 1 } 346 } 347 return 0 348} 349 350func nx_char_is_alpha(c: u8) -> nx_int { 351 if c >= 65 { 352 if c <= 90 { return 1 } 353 } 354 if c >= 97 { 355 if c <= 122 { return 1 } 356 } 357 return 0 358} 359 360func nx_char_is_alnum(c: u8) -> nx_int { 361 if nx_char_is_digit(c) == 1 { return 1 } 362 if nx_char_is_alpha(c) == 1 { return 1 } 363 return 0 364} 365 366func nx_char_is_whitespace(c: u8) -> nx_int { 367 if c == 32 { return 1 } // space 368 if c == 9 { return 1 } // tab 369 if c == 10 { return 1 } // newline 370 if c == 13 { return 1 } // carriage return 371 if c == 11 { return 1 } // vertical tab 372 if c == 12 { return 1 } // form feed 373 return 0 374} 375 376// to_lower / to_upper (ASCII only). 377func nx_to_lower(c: u8) -> u8 { 378 if c >= 65 { 379 if c <= 90 { return c + 32 } 380 } 381 return c 382} 383 384func nx_to_upper(c: u8) -> u8 { 385 if c >= 97 { 386 if c <= 122 { return c - 32 } 387 } 388 return c 389} 390 391// Hex-nibble helpers (commonly needed for serialization). 392func nx_hex_to_nibble(c: u8) -> nx_int { 393 if c >= 48 { 394 if c <= 57 { return c - 48 } 395 } 396 if c >= 97 { 397 if c <= 102 { return c - 87 } 398 } 399 if c >= 65 { 400 if c <= 70 { return c - 55 } 401 } 402 return -1 403} 404 405func nx_nibble_to_hex(n: nx_int) -> u8 { 406 if n < 10 { return 48 + n } 407 return 87 + n // 'a' + (n - 10) 408}