lumbda.py: fix two Python-tier defects (quotient precision + reader inf coercion)
Two defects uncovered during secp256k1 oracle port; both forced workarounds downstream in ecdsa/lumbda/secp256k1.lsp that can now retire. (1) `quotient` used `int(a / b)` — Python float division. Past 2^53 the float lost precision, so mod-pow on secp256k1's 2^256 prime corrupted every modular inverse with off-by-one errors in the square-and-multiply loop. Replaced with R7RS-spec truncate-toward-zero integer division. Verified: (quotient 7 2)=3, (quotient -7 2)=-3, (quotient 7 -2)=-3, (quotient -7 -2)=3, (quotient 0 5)=0, (quotient 2305843009213693950 2) now returns 1152921504606846975 (was 1152921504606846976, off by 1). (2) Reader `_atom` looped `for conv in (int, float)` and Python's float() accepts bare 'inf', 'infinity', 'nan' as IEEE specials. So `'(infinity)` silently parsed as `(+inf.0)` and `(symbol? 'infinity)` returned #f. R7RS spells these +inf.0 / -inf.0 / +nan.0 explicitly. Restricted the float-parse path to a strict decimal/exponent regex; the named IEEE specials still match their proper spellings. Verified: (symbol? 'infinity)=#t (was #f), (symbol? 'inf)=#t, '(infinity) reads as the symbol list, (positive? +inf.0)=#t still works. Tests: 571 Python unit + 205 functional (Python + C) all pass.
This commit is contained in:
parent
a95277cef4
commit
98d0e35a4e
1 changed files with 10 additions and 4 deletions
14
lumbda.py
14
lumbda.py
|
|
@ -399,12 +399,18 @@ def _atom(t):
|
|||
if t.startswith('"'):
|
||||
return (t[1:-1].replace('\\"', '"').replace('\\n', '\n')
|
||||
.replace('\\t', '\t').replace('\\\\', '\\').replace('\\r', '\r'))
|
||||
for conv in (int, float):
|
||||
try: return conv(t)
|
||||
except (ValueError, OverflowError): pass
|
||||
try: return int(t)
|
||||
except (ValueError, OverflowError): pass
|
||||
# Float parse: gate against Python's float() accepting bare 'inf',
|
||||
# 'infinity', 'nan' as IEEE specials — R7RS spells those +inf.0 /
|
||||
# -inf.0 / +nan.0 explicitly. Without this guard, '(infinity) reads
|
||||
# as (+inf.0) and '(nan) reads as (+nan.0).
|
||||
if t == '+inf.0': return math.inf
|
||||
if t == '-inf.0': return -math.inf
|
||||
if t in ('+nan.0', '-nan.0'): return float('nan')
|
||||
if re.fullmatch(r'[+-]?(\d+\.\d*|\.\d+|\d+)([eE][+-]?\d+)?', t):
|
||||
try: return float(t)
|
||||
except (ValueError, OverflowError): pass
|
||||
# Rational literal n/d (e.g. 1/3, -2/5)
|
||||
if re.fullmatch(r'-?\d+/-?\d+', t):
|
||||
try:
|
||||
|
|
@ -2946,7 +2952,7 @@ def make_global_env():
|
|||
n = float(n) / float(x)
|
||||
return n
|
||||
d(S('/'), _div)
|
||||
d(S('quotient'), lambda a, _: int(_num(a[0]) / _num(a[1])))
|
||||
d(S('quotient'), lambda a, _: (lambda x, y: -(abs(int(x)) // abs(int(y))) if (x < 0) != (y < 0) else abs(int(x)) // abs(int(y)))(_num(a[0]), _num(a[1])))
|
||||
d(S('remainder'), lambda a, _: int(_num(a[0])) % int(_num(a[1])) * (1 if _num(a[0]) >= 0 else -1))
|
||||
d(S('modulo'), lambda a, _: int(_num(a[0])) % int(_num(a[1])))
|
||||
d(S('expt'), lambda a, _: _num(a[0]) ** _num(a[1]))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue