code wiki / (root) / nx_isqrt.nx

nx_isqrt.nx source

↩ module page · 142 lines · 6807 B

1// nx_isqrt.nx -- integer square root, shared helper. 2// 3// Per cardinal feedback-15 DRY-through-shared-libraries: the same 4// digit-by-digit binary-sqrt routine appeared inlined in 5// nx_henyey_greenstein.nx and nx_worley_noise.nx within hours of 6// each other. Third caller would have triggered "rule of three"; 7// factoring at two callers + planned third (nx_distance_field_2d, 8// nx_curl_noise magnitude, nx_atmosphere_scatter optical-depth) is 9// the right time. 10// 11// Algorithm: digit-by-digit binary square root (Wikipedia "Methods 12// of computing square roots", section "Binary numeral system (base 13// 2)"). Returns floor(sqrt(n)) for n >= 0; refuses (returns 0) for 14// n < 0. O(log n) iterations. No division, no multiplication 15// except shifts -- safe on every Nishi target without depending on 16// the host's idiv. 17// 18// Q10 wrapper: nx_isqrt_q10(x_q10) computes sqrt(x) where x is Q10- 19// scaled, returning the result also in Q10. Implementation: 20// sqrt(x) = sqrt(x_q10 / Q) = sqrt(x_q10) / sqrt(Q) = sqrt(x_q10) / 21// sqrt(Q). To recover Q10 scaling: sqrt(x_q10) * Q / sqrt(Q) = 22// sqrt(x_q10) * sqrt(Q) = sqrt(x_q10 * Q) -- this is the standard 23// Q-to-Q sqrt trick. For Q = 1024, sqrt(Q) = 32, so we can 24// alternatively compute nx_isqrt(x_q10) * 32 -- but the multiply- 25// then-sqrt form preserves more precision for small x_q10 values. 26// 27// Loss audit: floor() in the integer-sqrt step truncates the 28// fractional bit; max relative error 1 / floor(sqrt(n)) at the 29// smallest representable values, asymptotically 0% as n grows. 30// 31// genealogy_id: wikipedia_binary_isqrt + abacus_long_division_sqrt 32// lineage_id: nx_isqrt_digit_by_digit_binary 33// 34// nx_safety_envelope: 35// intended_use: "Integer square root (digit-by-digit binary) 36// + Q10 wrapper -- shared math primitive for 37// substrate kernels (noise, distance fields, 38// attenuation, atmospheric scattering)" 39// sil_target: SIL2 (math primitive; correctness affects 40// every consumer) 41// asil_target: QM 42// dal_target: DAL B 43// evidence: [Wikipedia_binary_isqrt_canonical, 44// no_FP, no_division, no_multiplication_except_shifts, 45// bit_equal_reproducible_across_targets, 46// floor_truncation_error_bounded_documented] 47// hazard_register: [bug-tape-negative-input-silent-return-zero, 48// bug-tape-i64-overflow-via-Q-multiply, 49// bug-tape-precision-loss-small-Q10-inputs] 50// residual_risk: "Negative inputs return 0 by design; 51// substrate cannot mathematically square-root 52// negatives without a complex-number type 53// (queued)." 54// verdict: NOT_YET_EVALUATED 55 56import "nx_syscalls.nx" 57import "nx_tier.nx" 58const NX_MAGIC_1024: i64 = 1024 59const NX_MAGIC_10000: i64 = 10000 60const NX_MAGIC_1048576: i64 = 1048576 61const NX_MAGIC_9999: i64 = 9999 62const NX_MAGIC_1000000000000: i64 = 1000000000000 63const NX_MAGIC_1000000: i64 = 1000000 64const NX_MAGIC_4096: i64 = 4096 65const NX_MAGIC_2048: i64 = 2048 66 67// ===== Raw integer sqrt ============================================= 68// Returns floor(sqrt(n)) for n >= 0. Returns 0 for n < 0. 69func nx_isqrt(n: nx_int) -> nx_int { 70 if n <= 0 { return 0 } 71 var x: nx_int = n 72 var c: nx_int = 0 73 var d: nx_int = 1 74 // Find the highest power of 4 not exceeding n. 75 while d <= n / 4 { d = d * 4 } 76 // Iterate digit-by-digit (base-2 grouped in pairs). 77 while d != 0 { 78 if x >= c + d { 79 x = x - c - d 80 c = c / 2 + d 81 } else { 82 c = c / 2 83 } 84 d = d / 4 85 } 86 return c 87} 88 89// ===== Q10 wrapper ================================================== 90// nx_isqrt_q10(x_q10) returns sqrt(x) in Q10 scale. Caller passes a 91// Q10-scaled value; result is Q10-scaled. Uses the precision- 92// preserving form sqrt(x_q10 * Q) which is equivalent to 93// nx_isqrt(x_q10) * sqrt(Q) but keeps more bits when x_q10 is small. 94// 95// Q = 1024 is the canonical scale used across the substrate. 96const NX_ISQRT_Q: nx_int = 1024 97 98func nx_isqrt_q10(x_q10: nx_int) -> nx_int { 99 if x_q10 <= 0 { return 0 } 100 return nx_isqrt(x_q10 * NX_ISQRT_Q) 101} 102 103// ===== Self-test ==================================================== 104func main() -> i64 { 105 // T1: Exact perfect squares. 106 if nx_isqrt(0) != 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 107 if nx_isqrt(1) != 1 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 108 if nx_isqrt(4) != 2 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 109 if nx_isqrt(9) != 3 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 110 if nx_isqrt(16) != 4 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 111 if nx_isqrt(100) != 10 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 112 if nx_isqrt(NX_MAGIC_1024) != 32 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 113 if nx_isqrt(NX_MAGIC_10000) != 100 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 114 if nx_isqrt(NX_MAGIC_1048576) != NX_MAGIC_1024 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 115 116 // T2: Floor behavior on non-squares. 117 if nx_isqrt(2) != 1 { return __syscall(93, 10, 0, 0, 0, 0, 0) } // sqrt(2) ~ 1.414 118 if nx_isqrt(3) != 1 { return __syscall(93, 11, 0, 0, 0, 0, 0) } // sqrt(3) ~ 1.732 119 if nx_isqrt(8) != 2 { return __syscall(93, 12, 0, 0, 0, 0, 0) } // sqrt(8) ~ 2.828 120 if nx_isqrt(15) != 3 { return __syscall(93, 13, 0, 0, 0, 0, 0) } // sqrt(15) ~ 3.872 121 if nx_isqrt(99) != 9 { return __syscall(93, 14, 0, 0, 0, 0, 0) } // sqrt(99) ~ 9.949 122 123 // T3: Negative inputs refuse with 0 return. 124 if nx_isqrt(0 - 1) != 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 125 if nx_isqrt(0 - NX_MAGIC_9999) != 0 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 126 127 // T4: Large inputs (within i64 range). sqrt(10^12) = 10^6. 128 if nx_isqrt(NX_MAGIC_1000000000000) != NX_MAGIC_1000000 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 129 130 // T5: Q10 wrapper -- sqrt(1.0) at Q10 = sqrt(1024 in Q10) = 1024. 131 if nx_isqrt_q10(NX_MAGIC_1024) != NX_MAGIC_1024 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 132 // sqrt(4.0) at Q10 = 2.0 in Q10 = 2048. Q10(4) = 4096. 133 if nx_isqrt_q10(NX_MAGIC_4096) != NX_MAGIC_2048 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 134 // sqrt(0.25) at Q10 = 0.5 in Q10 = 512. Q10(0.25) = 256. 135 if nx_isqrt_q10(256) != 512 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 136 // sqrt(0) = 0. 137 if nx_isqrt_q10(0) != 0 { return __syscall(93, 43, 0, 0, 0, 0, 0) } 138 // Negative input refuses. 139 if nx_isqrt_q10(0 - NX_MAGIC_1024) != 0 { return __syscall(93, 44, 0, 0, 0, 0, 0) } 140 141 return 0 142}