isqrt: add integer square root builtin to all three impls

Semantics: (isqrt n) → floor(sqrt(n)). Negative argument errors.
Matches Python 3.8+ math.isqrt and R7RS exact-integer-sqrt contract.

- Python: wraps math.isqrt via lambda registration
- C: hand-rolled bit-by-bit algorithm in bi_isqrt (O(log n), no FPU)
- asm: new BI_ISQRT=109, bit-by-bit algorithm in integer registers
       (%r8/%r9/%r10). Negative input → stderr + exit(1) like other
       errors. GC builtin constants bumped to 110-114.

Tests:
- tests/functional.lsp: 7 shared tests (0, 1, perfect squares, floor
  cases, large values). Python + C now 196 each (was 189).
- asm/test.sh: 5 asm-local tests. asm suite now 142 (was 137).

MOAD: all three implementations O(log n), no O(N²) hazards.
This commit is contained in:
russell@unturf.com 2026-04-20 08:50:24 -04:00
parent f57de49f7b
commit 6e9d3ea52f
9 changed files with 98 additions and 8 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -194,15 +194,16 @@
.equ BI_HS_SIZE, 106
.equ BI_HS_LIST, 107
.equ BI_TCPSENDFILE, 108
.equ BI_ISQRT, 109
.ifdef GC_NAIVE
.equ BI_GC_COLLECT, 109
.equ BI_GC_STATS, 110
.equ BI_WITH_ARENA, 111
.equ BI_ARENA_STATS, 112
.equ BI_ARENA_SET_MODE, 113
.equ BI_COUNT, 114
.equ BI_GC_COLLECT, 110
.equ BI_GC_STATS, 111
.equ BI_WITH_ARENA, 112
.equ BI_ARENA_STATS, 113
.equ BI_ARENA_SET_MODE, 114
.equ BI_COUNT, 115
.else
.equ BI_COUNT, 109
.equ BI_COUNT, 110
.endif
# ============================================================
@ -299,6 +300,7 @@ bn_listp: .byte 5; .ascii "list?"
bn_substr: .byte 9; .ascii "substring"
bn_expt: .byte 4; .ascii "expt"
bn_gcd: .byte 3; .ascii "gcd"
bn_isqrt: .byte 5; .ascii "isqrt"
bn_integerp: .byte 8; .ascii "integer?"
bn_portalsave: .byte 11; .ascii "portal-save"
bn_portalresume:.byte 13; .ascii "portal-resume"
@ -388,6 +390,7 @@ bi_names:
.quad bn_htexists, bn_htsize, bn_htkeys, bn_htvals, bn_htalist
.quad bn_hsmake, bn_hsp, bn_hsadd, bn_hshas, bn_hssize, bn_hslist
.quad bn_tcpsendfile
.quad bn_isqrt
.ifdef GC_NAIVE
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
.endif
@ -399,6 +402,8 @@ err_notproc: .ascii "Error: not a procedure\n"
.equ err_notproc_len, . - err_notproc
err_oom: .ascii "Error: out of memory\n"
.equ err_oom_len, . - err_oom
err_isqrt_neg: .ascii "Error: isqrt: negative argument\n"
.equ err_isqrt_neg_len, . - err_isqrt_neg
# Print strings
s_true: .ascii "#t"
@ -3355,6 +3360,8 @@ eval_list:
je bi_expt
cmpq $BI_GCD, %rax
je bi_gcd
cmpq $BI_ISQRT, %rax
je bi_isqrt
cmpq $BI_INTEGERP, %rax
je bi_integerp
cmpq $BI_PORTALSAVE, %rax
@ -4663,6 +4670,58 @@ bi_gcd:
call make_int
RET_VAL
# bi_isqrt: floor of integer square root (bit-by-bit, no FPU).
# Negative input -> error exit. Registers: %r8 = x, %r9 = res, %r10 = bit.
bi_isqrt:
GETARG %rax
sarq $3, %rax # untag
testq %rax, %rax
js .bisqrt_neg
cmpq $2, %rax
jl .bisqrt_small # v < 2 -> result is v itself
movq %rax, %r8 # x
xorq %r9, %r9 # res
movq $1, %r10
shlq $62, %r10 # bit = 1<<62
.bisqrt_align:
cmpq %r8, %r10
jbe .bisqrt_loop
shrq $2, %r10
jmp .bisqrt_align
.bisqrt_loop:
testq %r10, %r10
jz .bisqrt_ret
movq %r9, %rcx
addq %r10, %rcx # rcx = res + bit
cmpq %rcx, %r8
jb .bisqrt_no
subq %rcx, %r8 # x -= res + bit
shrq $1, %r9
addq %r10, %r9 # res = (res>>1) + bit
jmp .bisqrt_next
.bisqrt_no:
shrq $1, %r9 # res >>= 1
.bisqrt_next:
shrq $2, %r10 # bit >>= 2
jmp .bisqrt_loop
.bisqrt_ret:
movq %r9, %rdi
call make_int
RET_VAL
.bisqrt_small:
movq %rax, %rdi
call make_int
RET_VAL
.bisqrt_neg:
movq $SYS_WRITE, %rax
movq $2, %rdi
leaq err_isqrt_neg(%rip), %rsi
movq $err_isqrt_neg_len, %rdx
syscall
movq $SYS_EXIT, %rax
movq $1, %rdi
syscall
bi_vector:
# (vector e1 e2 ...) build from remaining args in %r12
# Count args

View file

@ -52,6 +52,11 @@ check "max" "(max 3 1 4)" "4"
check "expt" "(* 2 2 2 2 2 2 2 2 2 2)" "1024"
check "div" "(/ 10 2)" "5"
check "expt-bi" "(expt 2 10)" "1024"
check "isqrt-0" "(isqrt 0)" "0"
check "isqrt-1" "(isqrt 1)" "1"
check "isqrt-perfect" "(isqrt 144)" "12"
check "isqrt-floor" "(isqrt 10)" "3"
check "isqrt-big" "(isqrt 1000000000000)" "1000000"
check "odd?" "(odd? 3)" "#t"
check "odd?-even" "(odd? 4)" "#f"
check "even?" "(even? 4)" "#t"

View file

@ -70,6 +70,24 @@ static Value bi_div(Value *a, int n, Env *e) {
}
MATH_1(sqrt, sqrt)
static Value bi_isqrt(Value *a, int n, Env *e) {
(void)e; CHECK_ARITY("isqrt", 1);
int64_t v = as_number_int(AS_NUM(a[0]));
if (v < 0) lisp_error("isqrt: negative argument: %lld", (long long)v);
if (v < 2) return VAL_INT(v);
uint64_t x = (uint64_t)v;
uint64_t res = 0;
uint64_t bit = (uint64_t)1 << 62;
while (bit > x) bit >>= 2;
while (bit != 0) {
if (x >= res + bit) { x -= res + bit; res = (res >> 1) + bit; }
else { res >>= 1; }
bit >>= 2;
}
return VAL_INT((int64_t)res);
}
MATH_1(sin, sin)
MATH_1(cos, cos)
MATH_1(tan, tan)
@ -1752,7 +1770,7 @@ Env *make_global_env(void) {
DEF("expt", bi_expt); DEF("abs", bi_abs);
DEF("floor", bi_floor); DEF("ceiling", bi_ceiling); DEF("round", bi_round);
DEF("truncate", bi_truncate);
DEF("sqrt", bi_sqrt); DEF("log", bi_log); DEF("exp", bi_exp);
DEF("sqrt", bi_sqrt); DEF("isqrt", bi_isqrt); DEF("log", bi_log); DEF("exp", bi_exp);
DEF("sin", bi_sin); DEF("cos", bi_cos); DEF("tan", bi_tan);
DEF("asin", bi_asin); DEF("acos", bi_acos); DEF("atan", bi_atan);
DEF("min", bi_min); DEF("max", bi_max);

View file

@ -2871,6 +2871,7 @@ def make_global_env():
d(S('floor/'), lambda a, _: (math.floor(_num(a[0]) / _num(a[1])),
_num(a[0]) - _num(a[1]) * math.floor(_num(a[0]) / _num(a[1]))))
d(S('sqrt'), lambda a, _: math.sqrt(_num(a[0])))
d(S('isqrt'), lambda a, _: math.isqrt(int(_num(a[0]))))
d(S('log'), lambda a, _: math.log(_num(a[0])) if len(a) == 1 else math.log(_num(a[0]), _num(a[1])))
d(S('exp'), lambda a, _: math.exp(_num(a[0])))
d(S('sin'), lambda a, _: math.sin(_num(a[0])))

View file

@ -50,6 +50,13 @@
(assert-equal "max" (max 3 1 4 1 5) 5)
(assert-equal "modulo" (modulo 10 3) 1)
(assert-equal "expt" (expt 2 10) 1024)
(assert-equal "isqrt-0" (isqrt 0) 0)
(assert-equal "isqrt-1" (isqrt 1) 1)
(assert-equal "isqrt-perfect" (isqrt 144) 12)
(assert-equal "isqrt-floor" (isqrt 10) 3)
(assert-equal "isqrt-just-below" (isqrt 99) 9)
(assert-equal "isqrt-just-above" (isqrt 101) 10)
(assert-equal "isqrt-large" (isqrt 1000000000000) 1000000)
;;; ═══════════════════════════════════════════════════════════════
;;; Comparison