From f3a3930061dbd91236ce6a874bf263fcd4cdbc99 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 4 Sep 2025 03:05:38 -0400 Subject: [PATCH 01/36] Added tests/jsnum-test.js to test methods in js-numbers.js that aren't exercised by Pyret --- tests/jsnums-test/jsnums-test.js | 89 ++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/jsnums-test/jsnums-test.js diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js new file mode 100644 index 000000000..4542c7e45 --- /dev/null +++ b/tests/jsnums-test/jsnums-test.js @@ -0,0 +1,89 @@ +// To test: Build Pyret, then cd to this directory, and type +// node jsnums-test.js + +var Jasmine = require('jasmine'); +var jazz = new Jasmine(); +const R = require("requirejs"); +var build = process.env["PHASE"] || "build/phaseA"; +R.config({ + waitSeconds: 15000, + paths: { + "trove": "../../" + build + "/trove", + "js": "../../" + build + "/js", + "compiler": "../../" + build + "/arr/compiler", + "jglr": "../../lib/jglr", + "pyret-base": "../../" + build + } +}); +R(["pyret-base/js/js-numbers"], function(JN) { + var sampleErrorBacks = { + throwDomainError: function() { throw 'domainError'; }, + throwLogNonPositive: function() { throw 'logNonPositive'; }, + }; + function test(actual, expected, testname, toks) { + if (actual === expected) { + return true; + } else { + var allToks = "Str was " + JSON.stringify(testname) + "\n"; + for (var t = 0; t < toks.length; t++) { + if (t > 0) allToks += "\n"; + allToks += "Tok[" + t + "] = " + toks[t].toString(true) + + " at pos " + toks[t].pos.toString(true); + } + allToks += "Expected " + JSON.stringify(expected) + ", but got " + JSON.stringify(actual) + + " in " + JSON.stringify(testname); + expect(allToks).toBe(""); + return false; + } + } + function testPos(tok, expected, str, toks) { + if (tok.pos.endChar - tok.pos.startChar == expected.length) { + return true; + } else { + test(str.slice(tok.pos.startChar, tok.pos.endChar), expected, str, toks); + return false; + } + } + describe("check exceptions in js-numbers methods that can't be tested in Pyret", function() { + it("bnpExp", function() { + // BigInteger.*.expt calls bnPow, wch calls bnpExp + // shd raise exc for too-large + expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrorBacks); }).toThrow('domainError'); + + // BigInteger.*.log + // shd raise exc for arg <= 0 + expect(function() { JN.makeBignum(-1).log(sampleErrorBacks); }).toThrow('logNonPositive'); + + // BigInteger.*asin + // shd raise exc for arg < -1 or > 1 + expect(function() { JN.makeBignum(-1.5).asin(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(+1.5).asin(sampleErrorBacks); }).toThrow('domainError'); + + // BigInteger.*acos + // shd raise exc for arg < -1 or > 1 + expect(function() { JN.makeBignum(-1.5).acos(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(+1.5).acos(sampleErrorBacks); }).toThrow('domainError'); + + // BigInteger.*.atan + // should work + expect(JN.makeBignum(0).atan(sampleErrorBacks)).toEqual(0); + + // BigInteger.*.sin + // should work + expect(JN.makeBignum(0).sin(sampleErrorBacks)).toEqual(0); + + // BigInteger.*.cos + // should work + expect(JN.makeBignum(0).cos(sampleErrorBacks)).toEqual(1); + + // BigInteger.*.tan + // should work + expect(JN.makeBignum(0).tan(sampleErrorBacks)).toEqual(0); + + + }); + }); + + jazz.execute(); + +}); From a6485eb234de6d4b40e8d11f4931b1eeecb385a9 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Tue, 16 Sep 2025 09:18:37 -0400 Subject: [PATCH 02/36] - jsnums-test.js: enhance #1812 - fromString(): for non-negative-exp sci not, avoid a division --- src/js/base/js-numbers.js | 28 ++++++++++- tests/jsnums-test/jsnums-test.js | 79 +++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d7ef80bc7..aaaee3b1e 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1201,9 +1201,11 @@ define("pyret-base/js/js-numbers", function() { // _integerMultiply: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerMultiply = makeIntegerBinop( function(m, n) { + // console.log('doing _integerMultiplyI', m, n); return m * n; }, function(m, n) { + // console.log('doing _integerMultiplyII[_,40]', m, n['40']); return bnMultiply.call(m, n); }); @@ -1453,6 +1455,7 @@ define("pyret-base/js/js-numbers", function() { }; Rational.makeInstance = function(n, d, errbacks) { + // console.log('doing rat.makeinst of', n, d); if (n === undefined) errbacks.throwUndefinedValue("n undefined", n, d); @@ -2046,6 +2049,7 @@ define("pyret-base/js/js-numbers", function() { // fromString: string -> (pyretnum | false) var fromString = function(x, errbacks) { + // console.log('doing fromString', x); if (x.match(digitRegexp)) { var n = Number(x); if (isOverflow(n)) { @@ -2070,6 +2074,7 @@ define("pyret-base/js/js-numbers", function() { if (beforeDecimalString !== '') { beforeDecimal = fromString(beforeDecimalString); } + // console.log('beforeDecimal =', beforeDecimal); // var afterDecimalString = aMatch[3]; var denominatorTen = 1; @@ -2081,6 +2086,8 @@ define("pyret-base/js/js-numbers", function() { afterDecimal = fromString(afterDecimalString); } } + // console.log('afterDecimal =', afterDecimal); + // console.log('denominatorTen =', denominatorTen); // var exponentString = aMatch[4]; var exponentNegativeP = false; @@ -2094,21 +2101,36 @@ define("pyret-base/js/js-numbers", function() { } exponent = fromString('1' + new Array(Number(exponentString) + 1).join('0')); } + // console.log('exponent[40] =', exponent['40']); var finalDen = denominatorTen; + // console.log('calling _integerMultiply'); var finalNum = _integerAdd(_integerMultiply(beforeDecimal, denominatorTen), afterDecimal); + // console.log('finalNum1 =', finalNum); if (negativeP) { finalNum = negate(finalNum, errbacks); } + // console.log('finalNum2 =', finalNum); // if (!equals(exponent, 1)) { if (exponentNegativeP) { finalDen = _integerMultiply(finalDen, exponent); + // finalDen = canonicalizeBignum(finalDen); } else { finalNum = _integerMultiply(finalNum, exponent); + // finalNum = canonicalizeBignum(finalNum); } } - return Rational.makeInstance(finalNum, finalDen, errbacks); + // console.log('finalNum3 =', finalNum); + // console.log('finalNum.40 =', finalNum['40']); + // console.log('finalDen =', finalDen); + var result; + if (finalDen === 1) { + result = finalNum; + } else { + result = Rational.makeInstance(finalNum, finalDen, errbacks); + } + return result; } aMatch = x.match(roughnumRatRegexp); @@ -2396,6 +2418,7 @@ define("pyret-base/js/js-numbers", function() { // (public) Constructor function BigInteger(a,b,c) { + // console.log('doing BigInteger of', a, '(', a? a.length : 'x', ')', b,c); if(a != null) if("number" == typeof a) this.fromNumber(a,b,c); else if(b == null && "string" != typeof a) this.fromString(a,256); @@ -3034,6 +3057,7 @@ define("pyret-base/js/js-numbers", function() { // (protected) alternate constructor function bnpFromNumber(a,b,c) { + // console.log('doing bnpFromNumber', a,b,c); if("number" == typeof b) { // new BigInteger(int,int,RNG) if(a < 2) this.fromInt(1); @@ -3622,8 +3646,10 @@ define("pyret-base/js/js-numbers", function() { // makeBignum: string -> BigInteger var makeBignum = function(s) { + // console.log('doing makeBignum', s); if (typeof(s) === 'number') { s = s + ''; } s = expandExponent(s); + // console.log('s became', s, 'of length', s.length); return new BigInteger(s, 10); }; diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 4542c7e45..4eafd81a5 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -44,22 +44,84 @@ R(["pyret-base/js/js-numbers"], function(JN) { return false; } } - describe("check exceptions in js-numbers methods that can't be tested in Pyret", function() { + describe("check functions that don't allow testing via Pyret programs", function() { + + it("fromString", function() { + expect(JN.fromString("5", sampleErrorBacks)).toEqual(5); + + var bigIntStr = "1" + new Array(309 + 1).join("0"); // 1 followed by 309 0s + expect(JN.fromString(bigIntStr, sampleErrorBacks)).toEqual(JN.makeBignum(bigIntStr)); + + // console.log(JN.fromString("1e1", sampleErrorBacks)); + // console.log(JN.fromString("10", sampleErrorBacks)); + // console.log(JN.makeBignum("1e1", sampleErrorBacks)); + // console.log(JN.makeBignum("10", sampleErrorBacks).toFixnum()); + + expect(JN.fromString("1e1", sampleErrorBacks)).toBe(10); + expect(JN.fromString("1e30", sampleErrorBacks)).toEqual(JN.makeBignum("1e30")); + expect(JN.fromString("1e140", sampleErrorBacks)).toEqual(JN.makeBignum("1e140")); + + // for large bignums (> 1e140 ?), fromString() and makeBignum() can give structurally + // unequal results. so the following fail: + // expect(JN.fromString("1e141", sampleErrorBacks)).toEqual(JN.makeBignum("1e141")); + // expect(JN.fromString("1e307", sampleErrorBacks)).toEqual(JN.makeBignum("1e307")); + // expect(JN.fromString("1e309", sampleErrorBacks)).toEqual(JN.makeBignum("1e309")); + + // but they're operationally equivalent! + expect(JN.equals(JN.fromString("1e141", sampleErrorBacks), + JN.makeBignum("1e141"), + sampleErrorBacks)).toBe(true); + + // fromString() and makeBignum() give different but operationally same bignums + // console.log('*************************'); + // console.log(JN.fromString("1e307", sampleErrorBacks)); + // console.log('-------------------------'); + // console.log(JN.makeBignum("1e307")); + // console.log('-------------------------'); + // console.log(JN.multiply(1, JN.makeBignum("1e307"), sampleErrorBacks)['40']); + // console.log('*************************'); + + expect(JN.fromString("1e311", sampleErrorBacks)).toEqual(JN.makeBignum("1e311")); + expect(JN.fromString("1/2", sampleErrorBacks)).toEqual(JN.makeRational(1, 2)); + expect(JN.fromString("355/113", sampleErrorBacks)).toEqual(JN.makeRational(355, 113)); + expect(JN.fromString("1.5e3", sampleErrorBacks)).toEqual(1500); + expect(JN.fromString("~2.718281828", sampleErrorBacks)).toEqual(JN.makeRoughnum(2.718281828)); + expect(JN.fromString("not-a-string", sampleErrorBacks)).toBe(false); + + }); + + it("fromFixnum", function() { + + expect(JN.fromFixnum(5, sampleErrorBacks)).toEqual(5); + expect(JN.fromFixnum(1/2, sampleErrorBacks)).toEqual(JN.makeRational(1, 2)); + expect(JN.fromFixnum(1.5e3, sampleErrorBacks)).toEqual(1500); + expect(JN.fromFixnum(1e311, sampleErrorBacks)).toBe(false); + + }); + it("bnpExp", function() { // BigInteger.*.expt calls bnPow, wch calls bnpExp - // shd raise exc for too-large + // shd raise exc for too-large expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrorBacks); }).toThrow('domainError'); - // BigInteger.*.log + // BigInteger.*.log // shd raise exc for arg <= 0 expect(function() { JN.makeBignum(-1).log(sampleErrorBacks); }).toThrow('logNonPositive'); + }); - // BigInteger.*asin - // shd raise exc for arg < -1 or > 1 + it("arithmetic", function() { + + }); + + it("trig functions", function() { + // BigInteger.*asin + // shd raise exception for arg outside [-1, +1] + // but this is not testable via Pyret, because args are always sane + // by the time this method is called expect(function() { JN.makeBignum(-1.5).asin(sampleErrorBacks); }).toThrow('domainError'); expect(function() { JN.makeBignum(+1.5).asin(sampleErrorBacks); }).toThrow('domainError'); - // BigInteger.*acos + // BigInteger.*acos // shd raise exc for arg < -1 or > 1 expect(function() { JN.makeBignum(-1.5).acos(sampleErrorBacks); }).toThrow('domainError'); expect(function() { JN.makeBignum(+1.5).acos(sampleErrorBacks); }).toThrow('domainError'); @@ -68,6 +130,11 @@ R(["pyret-base/js/js-numbers"], function(JN) { // should work expect(JN.makeBignum(0).atan(sampleErrorBacks)).toEqual(0); + // atan2 (perhaps Pyret test is enough) + expect(function () { + JN.atan2(JN.makeBignum(0), JN.makeBignum(0), sampleErrorBacks); + }).toThrow('domainError'); + // BigInteger.*.sin // should work expect(JN.makeBignum(0).sin(sampleErrorBacks)).toEqual(0); From 812e409b81ce177ee0aa7e9d67cfd15e56d99d13 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Tue, 16 Sep 2025 12:30:50 -0400 Subject: [PATCH 03/36] fromString(): Rational.makeInstance already takes care of den == 1 --- src/js/base/js-numbers.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index aaaee3b1e..84be76ae9 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2124,13 +2124,7 @@ define("pyret-base/js/js-numbers", function() { // console.log('finalNum3 =', finalNum); // console.log('finalNum.40 =', finalNum['40']); // console.log('finalDen =', finalDen); - var result; - if (finalDen === 1) { - result = finalNum; - } else { - result = Rational.makeInstance(finalNum, finalDen, errbacks); - } - return result; + return result = Rational.makeInstance(finalNum, finalDen, errbacks); } aMatch = x.match(roughnumRatRegexp); From a1837bea126affff4d22728170597d3532b85943 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Tue, 16 Sep 2025 19:21:49 -0400 Subject: [PATCH 04/36] - makeNumericBinop tests - js-numbers.js returns _innards to allow more testing --- src/js/base/js-numbers.js | 7 +- tests/jsnums-test/jsnums-test.js | 145 +++++++++++++++++++------------ 2 files changed, 94 insertions(+), 58 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 84be76ae9..ae4ee784a 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2124,7 +2124,7 @@ define("pyret-base/js/js-numbers", function() { // console.log('finalNum3 =', finalNum); // console.log('finalNum.40 =', finalNum['40']); // console.log('finalDen =', finalDen); - return result = Rational.makeInstance(finalNum, finalDen, errbacks); + return Rational.makeInstance(finalNum, finalDen, errbacks); } aMatch = x.match(roughnumRatRegexp); @@ -4087,5 +4087,10 @@ define("pyret-base/js/js-numbers", function() { Numbers['MIN_FIXNUM'] = MIN_FIXNUM; Numbers['MAX_FIXNUM'] = MAX_FIXNUM; + // the following exposes innards for testing + Numbers['_innards'] = { + makeNumericBinop: makeNumericBinop, + }; + return Numbers; }); diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 4eafd81a5..17f0edc5e 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -16,9 +16,9 @@ R.config({ } }); R(["pyret-base/js/js-numbers"], function(JN) { - var sampleErrorBacks = { - throwDomainError: function() { throw 'domainError'; }, - throwLogNonPositive: function() { throw 'logNonPositive'; }, + var sampleErrbacks = { + throwDomainError: function(x) { throw new Error('domainError ' + x); }, + throwLogNonPositive: function(x) { throw new Error('logNonPositive ' + x); }, }; function test(actual, expected, testname, toks) { if (actual === expected) { @@ -46,67 +46,98 @@ R(["pyret-base/js/js-numbers"], function(JN) { } describe("check functions that don't allow testing via Pyret programs", function() { + it("makeNumericBinop", function() { + var bogusNumFun = JN._innards.makeNumericBinop( + function(x, y, errbacks) { + if (x <= 2 && y <= 2) return 1; + if (x <= 2) return 2; + if (y <= 2) return 3; + errbacks.throwDomainError('makeNumericBinop first fail'); + }, + function(x, y, errbacks) { + if (JN.lessThanOrEqual(x, 2, errbacks) && JN.lessThanOrEqual(y, 2, errbacks)) return 4; + if (JN.lessThanOrEqual(x, 2, errbacks)) return 5; + if (JN.lessThanOrEqual(y, 2, errbacks)) return 6; + errbacks.throwDomainError('makeNumericBinop second fail'); + }, + { + isXSpecialCase: function(x, errbacks) { + return JN.lessThanOrEqual(x, 0, errbacks); + }, + onXSpecialCase: function(x, y, errbacks) { + return 7; + }, + isYSpecialCase: function(y, errbacks) { + return JN.lessThanOrEqual(y, 0, errbacks); + }, + onYSpecialCase: function(x, y, errbacks) { + return 8; + } + } + ); + + expect(bogusNumFun(1, 1, sampleErrbacks)).toEqual(1); + expect(bogusNumFun(1, 3, sampleErrbacks)).toEqual(2); + expect(bogusNumFun(3, 1, sampleErrbacks)).toEqual(3); + expect(function() { bogusNumFun(3, 3, sampleErrbacks); }).toThrowError(/first fail/); + + expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)).toEqual(4); + expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks)).toEqual(5); + expect(bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)).toEqual(6); + expect(function() { bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks); }).toThrowError(/second fail/); + + expect(bogusNumFun(-1, +1, sampleErrbacks)).toEqual(7); + expect(bogusNumFun(+1, -1, sampleErrbacks)).toEqual(8); + + }); + it("fromString", function() { - expect(JN.fromString("5", sampleErrorBacks)).toEqual(5); + expect(JN.fromString("5", sampleErrbacks)).toEqual(5); var bigIntStr = "1" + new Array(309 + 1).join("0"); // 1 followed by 309 0s - expect(JN.fromString(bigIntStr, sampleErrorBacks)).toEqual(JN.makeBignum(bigIntStr)); - - // console.log(JN.fromString("1e1", sampleErrorBacks)); - // console.log(JN.fromString("10", sampleErrorBacks)); - // console.log(JN.makeBignum("1e1", sampleErrorBacks)); - // console.log(JN.makeBignum("10", sampleErrorBacks).toFixnum()); - - expect(JN.fromString("1e1", sampleErrorBacks)).toBe(10); - expect(JN.fromString("1e30", sampleErrorBacks)).toEqual(JN.makeBignum("1e30")); - expect(JN.fromString("1e140", sampleErrorBacks)).toEqual(JN.makeBignum("1e140")); - - // for large bignums (> 1e140 ?), fromString() and makeBignum() can give structurally - // unequal results. so the following fail: - // expect(JN.fromString("1e141", sampleErrorBacks)).toEqual(JN.makeBignum("1e141")); - // expect(JN.fromString("1e307", sampleErrorBacks)).toEqual(JN.makeBignum("1e307")); - // expect(JN.fromString("1e309", sampleErrorBacks)).toEqual(JN.makeBignum("1e309")); - - // but they're operationally equivalent! - expect(JN.equals(JN.fromString("1e141", sampleErrorBacks), - JN.makeBignum("1e141"), - sampleErrorBacks)).toBe(true); - - // fromString() and makeBignum() give different but operationally same bignums - // console.log('*************************'); - // console.log(JN.fromString("1e307", sampleErrorBacks)); - // console.log('-------------------------'); - // console.log(JN.makeBignum("1e307")); - // console.log('-------------------------'); - // console.log(JN.multiply(1, JN.makeBignum("1e307"), sampleErrorBacks)['40']); - // console.log('*************************'); - - expect(JN.fromString("1e311", sampleErrorBacks)).toEqual(JN.makeBignum("1e311")); - expect(JN.fromString("1/2", sampleErrorBacks)).toEqual(JN.makeRational(1, 2)); - expect(JN.fromString("355/113", sampleErrorBacks)).toEqual(JN.makeRational(355, 113)); - expect(JN.fromString("1.5e3", sampleErrorBacks)).toEqual(1500); - expect(JN.fromString("~2.718281828", sampleErrorBacks)).toEqual(JN.makeRoughnum(2.718281828)); - expect(JN.fromString("not-a-string", sampleErrorBacks)).toBe(false); + expect(JN.fromString(bigIntStr, sampleErrbacks)).toEqual(JN.makeBignum(bigIntStr)); + + expect(JN.fromString("1e1", sampleErrbacks)).toBe(10); + + // for sci-not, fromString() and makeBignum() can give structurally + // unequal but operationally equivalent results, so the following fails: + // expect(JN.fromString("1e141", sampleErrbacks)).toEqual(JN.makeBignum("1e141")); + // however you can refashion the test using JN.equals + + expect(JN.equals(JN.fromString("1e5", sampleErrbacks), JN.makeBignum("1e5"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e30", sampleErrbacks), JN.makeBignum("1e30"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e140", sampleErrbacks), JN.makeBignum("1e140"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e141", sampleErrbacks), JN.makeBignum("1e141"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e307", sampleErrbacks), JN.makeBignum("1e307"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e309", sampleErrbacks), JN.makeBignum("1e309"), sampleErrbacks)).toBe(true); + + + expect(JN.equals(JN.fromString("1e311", sampleErrbacks), JN.makeBignum("1e311"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1/2", sampleErrbacks), JN.makeRational(1, 2, sampleErrbacks), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("355/113", sampleErrbacks), JN.makeRational(355, 113, sampleErrbacks), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1.5e3", sampleErrbacks), 1500)).toBe(true); + expect(JN.roughlyEquals(JN.fromString("~2.71828", sampleErrbacks), JN.makeRoughnum(2.71828, sampleErrbacks), 0.001, sampleErrbacks)).toBe(true); + expect(JN.fromString("not-a-string", sampleErrbacks)).toBe(false); }); it("fromFixnum", function() { - expect(JN.fromFixnum(5, sampleErrorBacks)).toEqual(5); - expect(JN.fromFixnum(1/2, sampleErrorBacks)).toEqual(JN.makeRational(1, 2)); - expect(JN.fromFixnum(1.5e3, sampleErrorBacks)).toEqual(1500); - expect(JN.fromFixnum(1e311, sampleErrorBacks)).toBe(false); + expect(JN.fromFixnum(5, sampleErrbacks)).toEqual(5); + expect(JN.fromFixnum(1/2, sampleErrbacks)).toEqual(JN.makeRational(1, 2)); + expect(JN.fromFixnum(1.5e3, sampleErrbacks)).toEqual(1500); + expect(JN.fromFixnum(1e311, sampleErrbacks)).toBe(false); }); it("bnpExp", function() { // BigInteger.*.expt calls bnPow, wch calls bnpExp // shd raise exc for too-large - expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); }).toThrowError(/domainError/); // BigInteger.*.log // shd raise exc for arg <= 0 - expect(function() { JN.makeBignum(-1).log(sampleErrorBacks); }).toThrow('logNonPositive'); + expect(function() { JN.makeBignum(-1).log(sampleErrbacks); }).toThrowError(/logNonPositive/); }); it("arithmetic", function() { @@ -118,34 +149,34 @@ R(["pyret-base/js/js-numbers"], function(JN) { // shd raise exception for arg outside [-1, +1] // but this is not testable via Pyret, because args are always sane // by the time this method is called - expect(function() { JN.makeBignum(-1.5).asin(sampleErrorBacks); }).toThrow('domainError'); - expect(function() { JN.makeBignum(+1.5).asin(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(-1.5).asin(sampleErrbacks); }).toThrowError(/domainError/); + expect(function() { JN.makeBignum(+1.5).asin(sampleErrbacks); }).toThrowError(/domainError/); // BigInteger.*acos // shd raise exc for arg < -1 or > 1 - expect(function() { JN.makeBignum(-1.5).acos(sampleErrorBacks); }).toThrow('domainError'); - expect(function() { JN.makeBignum(+1.5).acos(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(-1.5).acos(sampleErrbacks); }).toThrowError(/domainError/); + expect(function() { JN.makeBignum(+1.5).acos(sampleErrbacks); }).toThrowError(/domainError/); // BigInteger.*.atan // should work - expect(JN.makeBignum(0).atan(sampleErrorBacks)).toEqual(0); + expect(JN.makeBignum(0).atan(sampleErrbacks)).toEqual(0); // atan2 (perhaps Pyret test is enough) expect(function () { - JN.atan2(JN.makeBignum(0), JN.makeBignum(0), sampleErrorBacks); - }).toThrow('domainError'); + JN.atan2(JN.makeBignum(0), JN.makeBignum(0), sampleErrbacks); + }).toThrowError(/domainError/); // BigInteger.*.sin // should work - expect(JN.makeBignum(0).sin(sampleErrorBacks)).toEqual(0); + expect(JN.makeBignum(0).sin(sampleErrbacks)).toEqual(0); // BigInteger.*.cos // should work - expect(JN.makeBignum(0).cos(sampleErrorBacks)).toEqual(1); + expect(JN.makeBignum(0).cos(sampleErrbacks)).toEqual(1); // BigInteger.*.tan // should work - expect(JN.makeBignum(0).tan(sampleErrorBacks)).toEqual(0); + expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); }); From f2d40ddb7d6fe8e7bd711cbca8aad1b68a7f554d Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 17 Sep 2025 12:38:33 -0400 Subject: [PATCH 05/36] jsnums-tests.js: Add tests for Rational methods --- tests/jsnums-test/jsnums-test.js | 117 ++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 17f0edc5e..fc87c92e1 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -19,6 +19,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { var sampleErrbacks = { throwDomainError: function(x) { throw new Error('domainError ' + x); }, throwLogNonPositive: function(x) { throw new Error('logNonPositive ' + x); }, + throwUndefinedValue: function(x) { throw new Error('undefinedValue ' + x); }, }; function test(actual, expected, testname, toks) { if (actual === expected) { @@ -111,7 +112,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.equals(JN.fromString("1e307", sampleErrbacks), JN.makeBignum("1e307"), sampleErrbacks)).toBe(true); expect(JN.equals(JN.fromString("1e309", sampleErrbacks), JN.makeBignum("1e309"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e311", sampleErrbacks), JN.makeBignum("1e311"), sampleErrbacks)).toBe(true); expect(JN.equals(JN.fromString("1/2", sampleErrbacks), JN.makeRational(1, 2, sampleErrbacks), sampleErrbacks)).toBe(true); expect(JN.equals(JN.fromString("355/113", sampleErrbacks), JN.makeRational(355, 113, sampleErrbacks), sampleErrbacks)).toBe(true); @@ -179,6 +179,121 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); + }); + + it("Rational methods", function() { + expect(function () { JN.Rational.makeInstance(undefined, undefined, sampleErrbacks); }) + .toThrowError(/undefined/); + expect(JN.equals(JN.Rational.makeInstance(1, undefined, sampleErrbacks), 1)).toBe(true); + expect(JN.equals(JN.Rational.makeInstance(1, -1, sampleErrbacks), -1)).toBe(true); + expect(JN.equals(JN.Rational.makeInstance(2, 1, sampleErrbacks), 2)).toBe(true); + expect(JN.equals(JN.Rational.makeInstance(0, 1, sampleErrbacks), 0)).toBe(true); + expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks), JN.fromString("2/3"))) + .toBe(true); + + expect(JN.Rational.makeInstance(1, 3, sampleErrbacks).equals( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).toString()).toBe("2/3"); + expect(JN.Rational.makeInstance(2, 1, sampleErrbacks).toString()).toBe("2"); + + + expect(JN.equals(JN.Rational.makeInstance(1, 3, sampleErrbacks).add( + JN.Rational.makeInstance(2, 3, sampleErrbacks), sampleErrbacks), + 1, sampleErrbacks)) + .toBe(true); + + expect(JN.equals(JN.Rational.makeInstance(4, 3, sampleErrbacks).subtract( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks), + 1, sampleErrbacks)) + .toBe(true); + + expect(JN.equals(JN.Rational.makeInstance(-4, 3, sampleErrbacks).negate(sampleErrbacks), + JN.fromString("4/3"), sampleErrbacks)) + .toBe(true); + + expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks).multiply( + JN.Rational.makeInstance(3, 2, sampleErrbacks), sampleErrbacks), + 1, sampleErrbacks)) + .toBe(true); + + expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks).divide( + JN.Rational.makeInstance(4, 6, sampleErrbacks), sampleErrbacks), + 1, sampleErrbacks)) + .toBe(true); + + // toRational? + expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).isRational()).toBe(true); + + expect(JN.Rational.makeInstance(2, 4, sampleErrbacks).toFixnum()).toEqual(0.5); + + + expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).numerator()).toEqual(2); + expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).denominator()).toEqual(3); + + expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).greaterThan( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).greaterThan( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Rational.makeInstance(1, 3, sampleErrbacks).greaterThanOrEqual( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Rational.makeInstance(1, 3, sampleErrbacks).lessThan( + JN.Rational.makeInstance(2, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Rational.makeInstance(1, 3, sampleErrbacks).lessThanOrEqual( + JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(101, 4, sampleErrbacks).integerSqrt(sampleErrbacks), + 5, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(100, 9, sampleErrbacks).sqrt(sampleErrbacks), + JN.Rational.makeInstance(10, 3, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(-4, 3, sampleErrbacks).abs(sampleErrbacks), + JN.Rational.makeInstance(4, 3, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(4, 3, sampleErrbacks).floor(sampleErrbacks), + 1, + sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(4, 3, sampleErrbacks).ceiling(sampleErrbacks), + 2, + sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(4, 3, sampleErrbacks).round(sampleErrbacks), + 1, + sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Rational.makeInstance(7, 2, sampleErrbacks).roundEven(sampleErrbacks), + 4, + sampleErrbacks)) + .toBe(true); + + }); }); From b8442a29e2ccab7329bc511f6c77d74d1572ef24 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 01:52:50 -0400 Subject: [PATCH 06/36] - more Rational.* tests - liftFixnumInteger: should produce valid Rational boxnum --- src/js/base/js-numbers.js | 4 +- tests/jsnums-test/jsnums-test.js | 109 +++++++++++++++++++++++++------ 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index ae4ee784a..a5d4a9f63 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -206,7 +206,7 @@ define("pyret-base/js/js-numbers", function() { else if (other instanceof BigInteger) return makeBignum(x); else - return new Rational(x, 1, errbacks); + return fromFixnum(x, errbacks); }; @@ -496,7 +496,7 @@ define("pyret-base/js/js-numbers", function() { return (((ex && ey) || (!ex && !ey)) && equals(x, y, errbacks)); }; - // approxEqual: pyretnum pyretnum pyretnum -> boolean + // approxEquals: pyretnum pyretnum pyretnum -> boolean var approxEquals = function(x, y, delta, errbacks) { return lessThanOrEqual(abs(subtract(x, y, errbacks), errbacks), delta, errbacks); diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index fc87c92e1..a8c70cdfd 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -82,10 +82,14 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(bogusNumFun(3, 1, sampleErrbacks)).toEqual(3); expect(function() { bogusNumFun(3, 3, sampleErrbacks); }).toThrowError(/first fail/); - expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)).toEqual(4); - expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks)).toEqual(5); - expect(bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)).toEqual(6); - expect(function() { bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks); }).toThrowError(/second fail/); + expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) + .toEqual(4); + expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks)) + .toEqual(5); + expect(bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) + .toEqual(6); + expect(function() { bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks); }) + .toThrowError(/second fail/); expect(bogusNumFun(-1, +1, sampleErrbacks)).toEqual(7); expect(bogusNumFun(+1, -1, sampleErrbacks)).toEqual(8); @@ -105,18 +109,36 @@ R(["pyret-base/js/js-numbers"], function(JN) { // expect(JN.fromString("1e141", sampleErrbacks)).toEqual(JN.makeBignum("1e141")); // however you can refashion the test using JN.equals - expect(JN.equals(JN.fromString("1e5", sampleErrbacks), JN.makeBignum("1e5"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e30", sampleErrbacks), JN.makeBignum("1e30"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e140", sampleErrbacks), JN.makeBignum("1e140"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e141", sampleErrbacks), JN.makeBignum("1e141"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e307", sampleErrbacks), JN.makeBignum("1e307"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1e309", sampleErrbacks), JN.makeBignum("1e309"), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e5", sampleErrbacks), JN.makeBignum("1e5"), sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1e30", sampleErrbacks), JN.makeBignum("1e30"), sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1e140", sampleErrbacks), JN.makeBignum("1e140"), sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1e141", sampleErrbacks), JN.makeBignum("1e141"), sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1e307", sampleErrbacks), JN.makeBignum("1e307"), sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1e309", sampleErrbacks), JN.makeBignum("1e309"), sampleErrbacks)) + .toBe(true); - expect(JN.equals(JN.fromString("1e311", sampleErrbacks), JN.makeBignum("1e311"), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("1/2", sampleErrbacks), JN.makeRational(1, 2, sampleErrbacks), sampleErrbacks)).toBe(true); - expect(JN.equals(JN.fromString("355/113", sampleErrbacks), JN.makeRational(355, 113, sampleErrbacks), sampleErrbacks)).toBe(true); + expect(JN.equals(JN.fromString("1e311", sampleErrbacks), + JN.makeBignum("1e311"), + sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("1/2", sampleErrbacks), + JN.makeRational(1, 2, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + expect(JN.equals(JN.fromString("355/113", sampleErrbacks), + JN.makeRational(355, 113, sampleErrbacks), + sampleErrbacks)) + .toBe(true); expect(JN.equals(JN.fromString("1.5e3", sampleErrbacks), 1500)).toBe(true); - expect(JN.roughlyEquals(JN.fromString("~2.71828", sampleErrbacks), JN.makeRoughnum(2.71828, sampleErrbacks), 0.001, sampleErrbacks)).toBe(true); + expect(JN.roughlyEquals(JN.fromString("~2.71828", sampleErrbacks), + JN.fromFixnum(2.71828, sampleErrbacks), + 0.001, sampleErrbacks)) + .toBe(true); expect(JN.fromString("not-a-string", sampleErrbacks)).toBe(false); }); @@ -133,7 +155,9 @@ R(["pyret-base/js/js-numbers"], function(JN) { it("bnpExp", function() { // BigInteger.*.expt calls bnPow, wch calls bnpExp // shd raise exc for too-large - expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); }).toThrowError(/domainError/); + expect(function() { + JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); + }).toThrowError(/domainError/); // BigInteger.*.log // shd raise exc for arg <= 0 @@ -178,7 +202,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { // should work expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); - }); it("Rational methods", function() { @@ -198,7 +221,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).toString()).toBe("2/3"); expect(JN.Rational.makeInstance(2, 1, sampleErrbacks).toString()).toBe("2"); - expect(JN.equals(JN.Rational.makeInstance(1, 3, sampleErrbacks).add( JN.Rational.makeInstance(2, 3, sampleErrbacks), sampleErrbacks), 1, sampleErrbacks)) @@ -228,7 +250,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.Rational.makeInstance(2, 4, sampleErrbacks).toFixnum()).toEqual(0.5); - expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).numerator()).toEqual(2); expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).denominator()).toEqual(3); @@ -267,7 +288,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.Rational.makeInstance(-4, 3, sampleErrbacks).abs(sampleErrbacks), JN.Rational.makeInstance(4, 3, sampleErrbacks), sampleErrbacks)) - .toBe(true); + .toBe(true); expect(JN.equals( JN.Rational.makeInstance(4, 3, sampleErrbacks).floor(sampleErrbacks), @@ -293,6 +314,56 @@ R(["pyret-base/js/js-numbers"], function(JN) { sampleErrbacks)) .toBe(true); + expect(JN.roughlyEquals( + JN.Rational.makeInstance(5, 2, sampleErrbacks).log(sampleErrbacks), + JN.fromFixnum(0.91629), + 0.001, sampleErrbacks)) + .toBe(true); + + // tan(pi/4) == 1 + expect(JN.roughlyEquals( + JN.Rational.makeInstance(355, 4 * 113, sampleErrbacks).tan(sampleErrbacks), + 1, 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(1000, 1732, sampleErrbacks).atan(sampleErrbacks), + JN.makeRoughnum(355 / (6 * 113)), + 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(1732, 1000, sampleErrbacks).atan(sampleErrbacks), + JN.fromFixnum(355 / (3 * 113)), + 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(355, 2 * 113, sampleErrbacks).cos(sampleErrbacks), + 0, 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(355, 2 * 113, sampleErrbacks).sin(sampleErrbacks), + 1, 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(9, 4, sampleErrbacks).expt( + JN.Rational.makeInstance(3, 2, sampleErrbacks), sampleErrbacks), + 27 / 8, 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(3, 2, sampleErrbacks).exp(sampleErrbacks), + JN.fromFixnum(Math.exp(1.5)), 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Rational.makeInstance(1, 2, sampleErrbacks).acos(sampleErrbacks), + JN.Rational.makeInstance(355, 3 * 113), + 0.001, sampleErrbacks)) + .toBe(true); }); }); From 6e0e0ef20b709143e2f9d0e6d644b0df96af769b Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 02:51:50 -0400 Subject: [PATCH 07/36] scrub unnecessary defs from jsnums-test.js --- tests/jsnums-test/jsnums-test.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index a8c70cdfd..8b48c5fae 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -8,10 +8,6 @@ var build = process.env["PHASE"] || "build/phaseA"; R.config({ waitSeconds: 15000, paths: { - "trove": "../../" + build + "/trove", - "js": "../../" + build + "/js", - "compiler": "../../" + build + "/arr/compiler", - "jglr": "../../lib/jglr", "pyret-base": "../../" + build } }); @@ -21,30 +17,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { throwLogNonPositive: function(x) { throw new Error('logNonPositive ' + x); }, throwUndefinedValue: function(x) { throw new Error('undefinedValue ' + x); }, }; - function test(actual, expected, testname, toks) { - if (actual === expected) { - return true; - } else { - var allToks = "Str was " + JSON.stringify(testname) + "\n"; - for (var t = 0; t < toks.length; t++) { - if (t > 0) allToks += "\n"; - allToks += "Tok[" + t + "] = " + toks[t].toString(true) - + " at pos " + toks[t].pos.toString(true); - } - allToks += "Expected " + JSON.stringify(expected) + ", but got " + JSON.stringify(actual) - + " in " + JSON.stringify(testname); - expect(allToks).toBe(""); - return false; - } - } - function testPos(tok, expected, str, toks) { - if (tok.pos.endChar - tok.pos.startChar == expected.length) { - return true; - } else { - test(str.slice(tok.pos.startChar, tok.pos.endChar), expected, str, toks); - return false; - } - } describe("check functions that don't allow testing via Pyret programs", function() { it("makeNumericBinop", function() { From 74efc26f9704fd4791cbc3a419e393e59ce28ff5 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 03:42:35 -0400 Subject: [PATCH 08/36] add Roughnum.* tests --- tests/jsnums-test/jsnums-test.js | 153 ++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 4 deletions(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 8b48c5fae..b525e8b1c 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -16,6 +16,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { throwDomainError: function(x) { throw new Error('domainError ' + x); }, throwLogNonPositive: function(x) { throw new Error('logNonPositive ' + x); }, throwUndefinedValue: function(x) { throw new Error('undefinedValue ' + x); }, + throwGeneralError: function(x) { throw new Error('generalError ' + x); }, }; describe("check functions that don't allow testing via Pyret programs", function() { @@ -225,10 +226,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).numerator()).toEqual(2); expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).denominator()).toEqual(3); - expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).greaterThan( - JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) - .toBe(true); - expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).greaterThan( JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) .toBe(true); @@ -337,7 +334,155 @@ R(["pyret-base/js/js-numbers"], function(JN) { 0.001, sampleErrbacks)) .toBe(true); + expect(JN.roughlyEquals( + JN.Rational.makeInstance(1, 2, sampleErrbacks).asin(sampleErrbacks), + JN.Rational.makeInstance(355, 6 * 113), + 0.001, sampleErrbacks)) + .toBe(true); + }); + + it("Roughnum methods", function() { + + expect(function () { JN.Roughnum.makeInstance(undefined, sampleErrbacks); }) + .toThrowError(/unsuitable/); + + expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(), 3.14)).toBe(true); + + expect(JN.roughlyEquals(JN.Roughnum.makeInstance(3.14, sampleErrbacks), 3.14, + 0.0001, sampleErrbacks)) + .toBe(true); + + expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).isRoughnum()).toBe(true); + + expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(), 3.14)).toBe(true); + + // shouldn't roughnum's numerator method take errbacks? + + expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).numerator().toFixnum()).toEqual(157); + expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).denominator().toFixnum()).toEqual(50); + + expect(JN.Roughnum.makeInstance(2.3, sampleErrbacks).greaterThan( + JN.Roughnum.makeInstance(1.3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Roughnum.makeInstance(1.3, sampleErrbacks).greaterThanOrEqual( + JN.Roughnum.makeInstance(1.3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Roughnum.makeInstance(1.3, sampleErrbacks).lessThan( + JN.Roughnum.makeInstance(2.3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + expect(JN.Roughnum.makeInstance(1.3, sampleErrbacks).lessThanOrEqual( + JN.Roughnum.makeInstance(1.3, sampleErrbacks), sampleErrbacks)) + .toBe(true); + + // why is roughnum integersqrt so different + + expect(function() { + JN.Roughnum.makeInstance(101, sampleErrbacks).integerSqrt(sampleErrbacks); + }).toThrowError(/can only be applied to an integer/); + + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(100, sampleErrbacks).sqrt(sampleErrbacks), + JN.Roughnum.makeInstance(10, sampleErrbacks), + 0.0001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(-3.14, sampleErrbacks).abs(sampleErrbacks), + JN.Roughnum.makeInstance(3.14, sampleErrbacks), + 0.0001, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).floor(sampleErrbacks), + 3, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).ceiling(sampleErrbacks), + 4, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).round(sampleErrbacks), + 3, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.5, sampleErrbacks).roundEven(sampleErrbacks), + 4, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(2.5, sampleErrbacks).log(sampleErrbacks), + JN.fromFixnum(0.91629), + 0.001, sampleErrbacks)) + .toBe(true); + + // tan(pi/4) == 1 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance((355 / (4 * 113)), sampleErrbacks).tan(sampleErrbacks), + 1, 0.001, sampleErrbacks)) + .toBe(true); + + // tan(pi/6) = 1/sqrt(3) + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(1/1.732, sampleErrbacks).atan(sampleErrbacks), + JN.fromFixnum(355 / (6 * 113), sampleErrbacks), + 0.001, sampleErrbacks)) + .toBe(true); + + // tan(pi/3) = sqrt(3) + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(1.732, sampleErrbacks).atan(sampleErrbacks), + JN.fromFixnum(355 / (3 * 113), sampleErrbacks), + 0.001, sampleErrbacks)) + .toBe(true); + + // cos(pi/2) = 0 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(355 / (2 * 113), sampleErrbacks).cos(sampleErrbacks), + 0, 0.001, sampleErrbacks)) + .toBe(true); + + // sin(pi/2) = 1 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(355 / (2 * 113), sampleErrbacks).sin(sampleErrbacks), + 1, 0.001, sampleErrbacks)) + .toBe(true); + + // (9/4)^(3/2) = 27/8 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(9/4, sampleErrbacks).expt( + JN.Roughnum.makeInstance(3/2, sampleErrbacks), sampleErrbacks), + 27 / 8, 0.001, sampleErrbacks)) + .toBe(true); + + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(3/2, sampleErrbacks).exp(sampleErrbacks), + JN.fromFixnum(Math.exp(1.5)), 0.001, sampleErrbacks)) + .toBe(true); + + // cos(pi/3) = 1/2 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(1/2, sampleErrbacks).acos(sampleErrbacks), + JN.Roughnum.makeInstance(355/(3 * 113), sampleErrbacks), + 0.001, sampleErrbacks)) + .toBe(true); + + // sin(pi/6) = 1/2 + expect(JN.roughlyEquals( + JN.Roughnum.makeInstance(1/2, sampleErrbacks).asin(sampleErrbacks), + JN.Roughnum.makeInstance(355/(6 * 113), sampleErrbacks), + 0.001, sampleErrbacks)) + .toBe(true); + + }) + + }); jazz.execute(); From bc4f1356959e28b235f0b8425bf337f3a4e8cb14 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 09:05:56 -0400 Subject: [PATCH 09/36] - add arrayEquals to help test structural equality - add test for toRepeatingDecimal --- tests/jsnums-test/jsnums-test.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index b525e8b1c..b6407400f 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -12,12 +12,26 @@ R.config({ } }); R(["pyret-base/js/js-numbers"], function(JN) { + var sampleErrbacks = { throwDomainError: function(x) { throw new Error('domainError ' + x); }, throwLogNonPositive: function(x) { throw new Error('logNonPositive ' + x); }, throwUndefinedValue: function(x) { throw new Error('undefinedValue ' + x); }, throwGeneralError: function(x) { throw new Error('generalError ' + x); }, }; + + function arrayEquals(A, B) { + if (A === B) return true; + if (!Array.isArray(A) || !Array.isArray(B)) return false; + // both are arrays + var n = A.length; + if (B.length !== n) return false; + for (var i = 0; i < n; i++) { + if (!arrayEquals(A[i], B[i])) return false; + } + return true; + } + describe("check functions that don't allow testing via Pyret programs", function() { it("makeNumericBinop", function() { @@ -137,7 +151,10 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(function() { JN.makeBignum(-1).log(sampleErrbacks); }).toThrowError(/logNonPositive/); }); - it("arithmetic", function() { + it("repeating decimal", function() { + + expect(arrayEquals(JN.toRepeatingDecimal(883, 700), ['1', '26', '142857'])) + .toBe(true); }); From 6beef5f70e6ab5885cbb9e25cd64853b1b3a99f2 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 18:39:58 -0400 Subject: [PATCH 10/36] - numerator, denominator methods take errbacks param - test toStringDigits --- src/js/base/js-numbers.js | 22 +++++++++++----------- tests/jsnums-test/jsnums-test.js | 32 +++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index a5d4a9f63..cf1f0b168 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -716,14 +716,14 @@ define("pyret-base/js/js-numbers", function() { var numerator = function(n, errbacks) { if (typeof(n) === 'number') return n; - return n.numerator(); + return n.numerator(errbacks); }; // denominator: pyretnum -> pyretnum var denominator = function(n, errbacks) { if (typeof(n) === 'number') return 1; - return n.denominator(); + return n.denominator(errbacks); }; // sqrt: pyretnum -> pyretnum @@ -1001,8 +1001,8 @@ define("pyret-base/js/js-numbers", function() { if (isInteger(x) && isInteger(y)) { return _integerRemainder(x, y); } else if (isRational(x) && isRational(y)) { - var xn = numerator(x); var xd = denominator(x); - var yn = numerator(y); var yd = denominator(y); + var xn = numerator(x, errbacks); var xd = denominator(x, errbacks); + var yn = numerator(y, errbacks); var yd = denominator(y, errbacks); var new_d = lcm(xd, [yd], errbacks); var new_xn = multiply(xn, divide(new_d, xd, errbacks), errbacks); var new_yn = multiply(yn, divide(new_d, yd, errbacks), errbacks); @@ -1576,11 +1576,11 @@ define("pyret-base/js/js-numbers", function() { return Roughnum.makeInstance(this.toFixnum(), errbacks); }; - Rational.prototype.numerator = function() { + Rational.prototype.numerator = function(errbacks) { return this.n; }; - Rational.prototype.denominator = function() { + Rational.prototype.denominator = function(errbacks) { return this.d; }; @@ -1897,7 +1897,7 @@ define("pyret-base/js/js-numbers", function() { return this; }; - Roughnum.prototype.numerator = function() { + Roughnum.prototype.numerator = function(errbacks) { var stringRep = this.n.toString(); var match = stringRep.match(/^(.*)\.(.*)$/); if (match) { @@ -1911,7 +1911,7 @@ define("pyret-base/js/js-numbers", function() { } }; - Roughnum.prototype.denominator = function() { + Roughnum.prototype.denominator = function(errbacks) { var stringRep = this.n.toString(); var match = stringRep.match(/^(.*)\.(.*)$/); if (match) { @@ -3741,11 +3741,11 @@ define("pyret-base/js/js-numbers", function() { } }; - BigInteger.prototype.numerator = function() { + BigInteger.prototype.numerator = function(errbacks) { return this; }; - BigInteger.prototype.denominator = function() { + BigInteger.prototype.denominator = function(errbacks) { return 1; }; @@ -3999,7 +3999,7 @@ define("pyret-base/js/js-numbers", function() { return ans; } // n is not an integer implies that d >= 1 - var decimal = toRepeatingDecimal(n.numerator(), n.denominator(), undefined, errbacks); + var decimal = toRepeatingDecimal(n.numerator(errbacks), n.denominator(errbacks), undefined, errbacks); var ans = decimal[1].toString(); while (ans.length < d) { ans += decimal[2]; diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index b6407400f..4d686cc4d 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -153,11 +153,25 @@ R(["pyret-base/js/js-numbers"], function(JN) { it("repeating decimal", function() { - expect(arrayEquals(JN.toRepeatingDecimal(883, 700), ['1', '26', '142857'])) + expect(arrayEquals(JN.toRepeatingDecimal(883, 700), ['1', '26', '142857'], + undefined, sampleErrbacks)) .toBe(true); }); + it("toStringDigits", function() { + + expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) + .toBe("123456789.00000"); + expect(JN.toStringDigits(123456789, -5, sampleErrbacks)) + .toBe("123500000"); + expect(JN.toStringDigits(JN.makeRational(355, 113, sampleErrbacks), 5, sampleErrbacks)) + .toBe("3.14159"); + expect(JN.toStringDigits(JN.makeRational(355 * 1e9, 113, sampleErrbacks), -5, sampleErrbacks)) + .toBe("3141600000"); + + }); + it("trig functions", function() { // BigInteger.*asin // shd raise exception for arg outside [-1, +1] @@ -240,8 +254,12 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.Rational.makeInstance(2, 4, sampleErrbacks).toFixnum()).toEqual(0.5); - expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).numerator()).toEqual(2); - expect(JN.Rational.makeInstance(4, 6, sampleErrbacks).denominator()).toEqual(3); + expect(JN.Rational.makeInstance(4, 6, sampleErrbacks) + .numerator(sampleErrbacks)) + .toEqual(2); + expect(JN.Rational.makeInstance(4, 6, sampleErrbacks) + .denominator(sampleErrbacks)) + .toEqual(3); expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).greaterThan( JN.Rational.makeInstance(1, 3, sampleErrbacks), sampleErrbacks)) @@ -376,8 +394,12 @@ R(["pyret-base/js/js-numbers"], function(JN) { // shouldn't roughnum's numerator method take errbacks? - expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).numerator().toFixnum()).toEqual(157); - expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).denominator().toFixnum()).toEqual(50); + expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks) + .numerator(sampleErrbacks).toFixnum()) + .toEqual(157); + expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks) + .denominator(sampleErrbacks).toFixnum()) + .toEqual(50); expect(JN.Roughnum.makeInstance(2.3, sampleErrbacks).greaterThan( JN.Roughnum.makeInstance(1.3, sampleErrbacks), sampleErrbacks)) From cc5277025a9abd27dc85d8114f742887314b19d1 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 22:29:40 -0400 Subject: [PATCH 11/36] jsnums-test.js: add test for gcd, lcm --- tests/jsnums-test/jsnums-test.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index f31240f70..75d7b3ac1 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -134,7 +134,10 @@ R(["pyret-base/js/js-numbers"], function(JN) { it("fromFixnum", function() { expect(JN.fromFixnum(5, sampleErrbacks)).toEqual(5); - expect(JN.equals(JN.fromFixnum(1/2, sampleErrbacks), JN.makeRational(1, 2))).toBe(true); + expect(JN.equals(JN.fromFixnum(1/2, sampleErrbacks), + JN.makeRational(1, 2, sampleErrbacks), + sampleErrbacks)) + .toBe(true); expect(JN.fromFixnum(1.5e3, sampleErrbacks)).toEqual(1500); expect(JN.fromFixnum(1e311, sampleErrbacks)).toBe(false); @@ -173,7 +176,13 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - it("trig functions", function() { + it("BigInteger methods", function() { + + expect(JN.equals( + JN.gcd(JN.makeBignum(24), [JN.makeBignum(30)], sampleErrbacks), + 6, sampleErrbacks)) + .toBe(true); + // BigInteger.*asin // shd raise exception for arg outside [-1, +1] // but this is not testable via Pyret, because args are always sane @@ -207,6 +216,16 @@ R(["pyret-base/js/js-numbers"], function(JN) { // should work expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); + expect(JN.equals( + JN.gcd(JN.makeBignum(24), [JN.makeBignum(30)], sampleErrbacks), + 6, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.lcm(JN.makeBignum(24), [JN.makeBignum(30)], sampleErrbacks), + 120, sampleErrbacks)) + .toBe(true); + }); it("Rational methods", function() { From cb4e69e7241afdb3df3ada407022185cc9c1b09d Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sun, 21 Sep 2025 01:23:54 -0400 Subject: [PATCH 12/36] - add tests for number predicates, sign, zfill, liftFixnumInteger - correct isInteger() def --- src/js/base/js-numbers.js | 8 +- tests/jsnums-test/jsnums-test.js | 164 ++++++++++++++++++++++++++----- 2 files changed, 143 insertions(+), 29 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index b52730f52..e97656c56 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -235,8 +235,9 @@ define("pyret-base/js/js-numbers", function() { // isInteger: pyretnum -> boolean var isInteger = function(n) { - return (typeof(n) === 'number' || - (isPyretNumber(n) && n.isInteger())); + if (typeof(n) === 'number') return Number.isInteger(n); + if (isPyretNumber(n)) return n.isInteger(); + return false; }; var isRoughnum = function(n) { @@ -4086,7 +4087,10 @@ define("pyret-base/js/js-numbers", function() { // the following exposes innards for testing Numbers['_innards'] = { + liftFixnumInteger: liftFixnumInteger, makeNumericBinop: makeNumericBinop, + sign: sign, + zfill: zfill, }; return Numbers; diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 75d7b3ac1..10da3ef7b 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -131,40 +131,107 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - it("fromFixnum", function() { - - expect(JN.fromFixnum(5, sampleErrbacks)).toEqual(5); - expect(JN.equals(JN.fromFixnum(1/2, sampleErrbacks), - JN.makeRational(1, 2, sampleErrbacks), - sampleErrbacks)) - .toBe(true); - expect(JN.fromFixnum(1.5e3, sampleErrbacks)).toEqual(1500); - expect(JN.fromFixnum(1e311, sampleErrbacks)).toBe(false); + it('number predicates', function() { + // isPyretNumber + expect(JN.isPyretNumber(5)).toBe(true); + expect(JN.isPyretNumber(-5)).toBe(true); + expect(JN.isPyretNumber(0)).toBe(true); + expect(JN.isPyretNumber(3.14)).toBe(true); + expect(JN.isPyretNumber(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isPyretNumber(JN.makeBignum(1e400))).toBe(true); + expect(JN.isPyretNumber(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); + + // isRational + expect(JN.isRational(5)).toBe(true); + expect(JN.isRational(-5)).toBe(true); + expect(JN.isRational(0)).toBe(true); + expect(JN.isRational(3.14)).toBe(true); + expect(JN.isRational(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isRational(JN.makeBignum(1e400))).toBe(true); + expect(JN.isRational(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + + // isExact + expect(JN.isExact(5)).toBe(true); + expect(JN.isExact(-5)).toBe(true); + expect(JN.isExact(0)).toBe(true); + expect(JN.isExact(3.14)).toBe(true); + expect(JN.isExact(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isExact(JN.makeBignum(1e400))).toBe(true); + expect(JN.isExact(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + + // isReal + expect(JN.isReal(5)).toBe(true); + expect(JN.isReal(-5)).toBe(true); + expect(JN.isReal(0)).toBe(true); + expect(JN.isReal(3.14)).toBe(true); + expect(JN.isReal(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isReal(JN.makeBignum(1e400))).toBe(true); + expect(JN.isReal(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); + + // isInteger + expect(JN.isInteger(5)).toBe(true); + expect(JN.isInteger(-5)).toBe(true); + expect(JN.isInteger(0)).toBe(true); + expect(JN.isInteger(3.14)).toBe(false); + expect(JN.isInteger(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); + expect(JN.isInteger(JN.makeBignum(1e400))).toBe(true); + expect(JN.isInteger(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + + // isRoughnum + expect(JN.isRoughnum(5)).toBe(false); + expect(JN.isRoughnum(-5)).toBe(false); + expect(JN.isRoughnum(0)).toBe(false); + expect(JN.isRoughnum(3.14)).toBe(false); + expect(JN.isRoughnum(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); + expect(JN.isRoughnum(JN.makeBignum(1e400))).toBe(false); + expect(JN.isRoughnum(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); + + // isPositive + expect(JN.isPositive(5)).toBe(true); + expect(JN.isPositive(-5)).toBe(false); + expect(JN.isPositive(0)).toBe(false); + expect(JN.isPositive(3.14)).toBe(true); + expect(JN.isPositive(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isPositive(JN.makeBignum(1e400))).toBe(true); + expect(JN.isPositive(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); + + // isNonPositive + expect(JN.isNonPositive(5)).toBe(false); + expect(JN.isNonPositive(-5)).toBe(true); + expect(JN.isNonPositive(0)).toBe(true); + expect(JN.isNonPositive(3.14)).toBe(false); + expect(JN.isNonPositive(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); + expect(JN.isNonPositive(JN.makeBignum(1e400))).toBe(false); + expect(JN.isNonPositive(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + + // isNegative + expect(JN.isNegative(5)).toBe(false); + expect(JN.isNegative(-5)).toBe(true); + expect(JN.isNegative(0)).toBe(false); + expect(JN.isNegative(3.14)).toBe(false); + expect(JN.isNegative(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); + expect(JN.isNegative(JN.makeBignum(1e400))).toBe(false); + expect(JN.isNegative(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + + // isNonNegative + expect(JN.isNonNegative(5)).toBe(true); + expect(JN.isNonNegative(-5)).toBe(false); + expect(JN.isNonNegative(0)).toBe(true); + expect(JN.isNonNegative(3.14)).toBe(true); + expect(JN.isNonNegative(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); + expect(JN.isNonNegative(JN.makeBignum(1e400))).toBe(true); + expect(JN.isNonNegative(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); }); - it("bnpExp", function() { - // BigInteger.*.expt calls bnPow, wch calls bnpExp - // shd raise exc for too-large - expect(function() { - JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); - }).toThrowError(/domainError/); - - // BigInteger.*.log - // shd raise exc for arg <= 0 - expect(function() { JN.makeBignum(-1).log(sampleErrbacks); }).toThrowError(/logNonPositive/); - }); - - it("repeating decimal", function() { + it('other subrs', function() { + // toRepeatingDecimal expect(arrayEquals(JN.toRepeatingDecimal(883, 700), ['1', '26', '142857'], undefined, sampleErrbacks)) .toBe(true); - }); - - it("toStringDigits", function() { - + // toStringDigits expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) .toBe("123456789.00000"); expect(JN.toStringDigits(123456789, -5, sampleErrbacks)) @@ -174,8 +241,40 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.toStringDigits(JN.makeRational(355 * 1e9, 113, sampleErrbacks), -5, sampleErrbacks)) .toBe("3141600000"); + // fromFixnum + expect(JN.fromFixnum(5, sampleErrbacks)).toEqual(5); + expect(JN.equals(JN.fromFixnum(1/2, sampleErrbacks), + JN.makeRational(1, 2, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + expect(JN.fromFixnum(1.5e3, sampleErrbacks)).toEqual(1500); + expect(JN.fromFixnum(1e311, sampleErrbacks)).toBe(false); + + // sign + expect(JN._innards.sign(JN.fromString('-3.14', sampleErrbacks))).toBe(-1); + expect(JN._innards.sign(JN.fromString('3.14', sampleErrbacks))).toBe(1); + expect(JN._innards.sign(JN.fromString('0.0', sampleErrbacks))).toBe(0); + + // zfill + expect(JN._innards.zfill(5)).toBe('00000'); + + // liftFixnumInteger + expect(JN.equals( + JN._innards.liftFixnumInteger(3.14, JN.Rational.makeInstance(1,2,sampleErrbacks), + sampleErrbacks), + JN.Rational.makeInstance(314, 100, sampleErrbacks), sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN._innards.liftFixnumInteger(3.14, JN.Roughnum.makeInstance(2,sampleErrbacks), + sampleErrbacks), + JN.Roughnum.makeInstance(3.14, sampleErrbacks), + 0.000001, + sampleErrbacks)) + .toBe(true); + }); + it("BigInteger methods", function() { expect(JN.equals( @@ -216,6 +315,17 @@ R(["pyret-base/js/js-numbers"], function(JN) { // should work expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); + + // BigInteger.*.expt calls bnPow, which calls bnpExp + // should raise exception for too-large + expect(function() { + JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); + }).toThrowError(/domainError/); + + // BigInteger.*.log + // should raise exception for arg <= 0 + expect(function() { JN.makeBignum(-1).log(sampleErrbacks); }).toThrowError(/logNonPositive/); + expect(JN.equals( JN.gcd(JN.makeBignum(24), [JN.makeBignum(30)], sampleErrbacks), 6, sampleErrbacks)) @@ -539,7 +649,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { 0.001, sampleErrbacks)) .toBe(true); - }) + }); }); From 1b7a85e0180532bbb06350b37668c10790ca2a46 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sun, 21 Sep 2025 04:28:55 -0400 Subject: [PATCH 13/36] add tests for number casts --- tests/jsnums-test/jsnums-test.js | 90 ++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 10da3ef7b..169d53109 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -138,7 +138,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isPyretNumber(0)).toBe(true); expect(JN.isPyretNumber(3.14)).toBe(true); expect(JN.isPyretNumber(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isPyretNumber(JN.makeBignum(1e400))).toBe(true); + expect(JN.isPyretNumber(JN.expt(10, 400))).toBe(true); expect(JN.isPyretNumber(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); // isRational @@ -147,7 +147,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isRational(0)).toBe(true); expect(JN.isRational(3.14)).toBe(true); expect(JN.isRational(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isRational(JN.makeBignum(1e400))).toBe(true); + expect(JN.isRational(JN.expt(10, 400))).toBe(true); expect(JN.isRational(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); // isExact @@ -156,7 +156,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isExact(0)).toBe(true); expect(JN.isExact(3.14)).toBe(true); expect(JN.isExact(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isExact(JN.makeBignum(1e400))).toBe(true); + expect(JN.isExact(JN.expt(10, 400))).toBe(true); expect(JN.isExact(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); // isReal @@ -165,7 +165,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isReal(0)).toBe(true); expect(JN.isReal(3.14)).toBe(true); expect(JN.isReal(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isReal(JN.makeBignum(1e400))).toBe(true); + expect(JN.isReal(JN.expt(10, 400))).toBe(true); expect(JN.isReal(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); // isInteger @@ -174,7 +174,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isInteger(0)).toBe(true); expect(JN.isInteger(3.14)).toBe(false); expect(JN.isInteger(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); - expect(JN.isInteger(JN.makeBignum(1e400))).toBe(true); + expect(JN.isInteger(JN.expt(10, 400))).toBe(true); expect(JN.isInteger(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); // isRoughnum @@ -183,7 +183,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isRoughnum(0)).toBe(false); expect(JN.isRoughnum(3.14)).toBe(false); expect(JN.isRoughnum(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); - expect(JN.isRoughnum(JN.makeBignum(1e400))).toBe(false); + expect(JN.isRoughnum(JN.expt(10, 400))).toBe(false); expect(JN.isRoughnum(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); // isPositive @@ -192,7 +192,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isPositive(0)).toBe(false); expect(JN.isPositive(3.14)).toBe(true); expect(JN.isPositive(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isPositive(JN.makeBignum(1e400))).toBe(true); + expect(JN.isPositive(JN.expt(10, 400))).toBe(true); expect(JN.isPositive(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); // isNonPositive @@ -201,7 +201,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isNonPositive(0)).toBe(true); expect(JN.isNonPositive(3.14)).toBe(false); expect(JN.isNonPositive(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); - expect(JN.isNonPositive(JN.makeBignum(1e400))).toBe(false); + expect(JN.isNonPositive(JN.expt(10, 400))).toBe(false); expect(JN.isNonPositive(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); // isNegative @@ -210,7 +210,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isNegative(0)).toBe(false); expect(JN.isNegative(3.14)).toBe(false); expect(JN.isNegative(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); - expect(JN.isNegative(JN.makeBignum(1e400))).toBe(false); + expect(JN.isNegative(JN.expt(10, 400))).toBe(false); expect(JN.isNegative(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); // isNonNegative @@ -219,11 +219,81 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isNonNegative(0)).toBe(true); expect(JN.isNonNegative(3.14)).toBe(true); expect(JN.isNonNegative(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(true); - expect(JN.isNonNegative(JN.makeBignum(1e400))).toBe(true); + expect(JN.isNonNegative(JN.expt(10, 400))).toBe(true); expect(JN.isNonNegative(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); }); + it('number casts', function() { + + // toFixnum (why no errbacks?) + expect(JN.toFixnum(5)).toBe(5); + expect(JN.toFixnum(-5)).toBe(-5); + expect(JN.toFixnum(0)).toBe(0); + expect(JN.toFixnum(3.14)).toBe(3.14); + expect(JN.toFixnum(JN.Rational.makeInstance(355, 113, sampleErrbacks))).toBe(355/113); + expect(JN.toFixnum(JN.expt(10, 400, sampleErrbacks))).toBe(Infinity); + expect(JN.toFixnum(JN.Roughnum.makeInstance(2.718, sampleErrbacks))).toBe(2.718); + + // toRational (toExact is its alias) + expect(JN.toRational(5, sampleErrbacks)).toBe(5); + expect(JN.toRational(-5, sampleErrbacks)).toBe(-5); + expect(JN.toRational(0, sampleErrbacks)).toBe(0); + expect(JN.toRational(3.14, sampleErrbacks)).toBe(3.14); + expect(JN.equals( + JN.toRational( + JN.Rational.makeInstance(355, 113, sampleErrbacks), sampleErrbacks), + JN.Rational.makeInstance(355, 113, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + expect(JN.equals( + JN.toRational(JN.expt(10, 400), sampleErrbacks), + JN.expt(10, 400, sampleErrbacks), + sampleErrbacks)) + .toBe(true); + expect(JN.equals( + JN.toRational(JN.Roughnum.makeInstance(2.718, sampleErrbacks)), + JN.fromString('2.718'), + sampleErrbacks)) + .toBe(true); + + // toRoughnum + expect(JN.roughlyEquals( + JN.toRoughnum(5, sampleErrbacks), + JN.fromString('~5'), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN.toRoughnum(-5, sampleErrbacks), + JN.fromString('~-5'), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN.toRoughnum(0, sampleErrbacks), + JN.fromString('~0'), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN.toRoughnum(3.14, sampleErrbacks), + JN.fromString('~3.14'), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN.toRoughnum( + JN.Rational.makeInstance(355, 113, sampleErrbacks), sampleErrbacks), + JN.Roughnum.makeInstance(355/113, sampleErrbacks), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(function() { + JN.roughlyEquals( + JN.toRoughnum(JN.expt(10, 400), sampleErrbacks), + JN.toFixnum(Infinity), + 0.00001, sampleErrbacks); + }) + .toThrowError(/domainError/); + + }); + it('other subrs', function() { // toRepeatingDecimal From e7916609aad867ea201253e319a5a7661201adcf Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 22 Sep 2025 15:34:20 -0400 Subject: [PATCH 14/36] - add tests for nthRoot() and integerNthRoot() #1812 - ensure integerNthRoot() is monotonically nondecreasing #1822 - require integerNthRoot() root and radicand to be non-neg - require nthRoot() root to be non-neg --- src/js/base/js-numbers.js | 10 ++++- tests/jsnums-test/jsnums-test.js | 75 +++++++++++++++++++++++++++----- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index e97656c56..e91254c26 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1710,8 +1710,12 @@ define("pyret-base/js/js-numbers", function() { }; var integerNthRoot = function(n, m, errbacks) { + if (sign(n) < 0) + errbacks.throwDomainError('integerNthRoot: root ' + n + ' is negative.'); + if (sign(m) < 0) + errbacks.throwDomainError('integerNthRoot: radicand ' + m + ' is negative.'); var guessPrev, guessToTheN; - var guess = m; + var guess = floor(m); // find closest integral zero of x^n - m = 0 using Newton-Raphson. // if k'th guess is x_k, then @@ -1733,6 +1737,8 @@ define("pyret-base/js/js-numbers", function() { }; var nthRoot = function(n, m, errbacks) { + if (sign(n) < 0) + errbacks.throwDomainError('nthRoot: root ' + n + ' is negative.'); var mNeg = (sign(m) < 0); var mAbs = (mNeg ? abs(m, errbacks) : m); var approx; @@ -4087,8 +4093,10 @@ define("pyret-base/js/js-numbers", function() { // the following exposes innards for testing Numbers['_innards'] = { + integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, makeNumericBinop: makeNumericBinop, + nthRoot: nthRoot, sign: sign, zfill: zfill, }; diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 169d53109..31b9b95dd 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -89,7 +89,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { var bigIntStr = "1" + new Array(309 + 1).join("0"); // 1 followed by 309 0s expect(JN.fromString(bigIntStr, sampleErrbacks)).toEqual(JN.makeBignum(bigIntStr)); - // for sci-not and 'p/q', fromString() and makeBignum()/makeRational() can give // structurally unequal but operationally equivalent results, so the following fails: // expect(JN.fromString("1e141", sampleErrbacks)).toEqual(JN.makeBignum("1e141")); @@ -290,7 +289,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.toFixnum(Infinity), 0.00001, sampleErrbacks); }) - .toThrowError(/domainError/); + .toThrowError(/overflow/); }); @@ -344,6 +343,64 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); + it("nthRoot integerNthRoot", function() { + expect(JN.equals( + JN._innards.nthRoot(3, 8, sampleErrbacks), + Math.pow(8, 1/3), + sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN._innards.nthRoot(3, 7.5, sampleErrbacks), + Math.pow(7.5, 1/3), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN._innards.nthRoot(3, 8.5, sampleErrbacks), + Math.pow(8.5, 1/3), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.equals( + JN._innards.nthRoot(3, -8, sampleErrbacks), + - Math.pow(8, 1/3), + sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN._innards.nthRoot(3, -7.5, sampleErrbacks), + - Math.pow(7.5, 1/3), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(JN.roughlyEquals( + JN._innards.nthRoot(3, -8.5, sampleErrbacks), + - Math.pow(8.5, 1/3), + 0.00001, sampleErrbacks)) + .toBe(true); + expect(function () { + JN._innards.nthRoot(-3, 8, sampleErrbacks); + }) + .toThrowError(/root .* negative/); + + expect(JN.equals( + JN._innards.integerNthRoot(3, 8, sampleErrbacks), + 2, sampleErrbacks)) + .toBe(true); + expect(JN.equals( + JN._innards.integerNthRoot(3, 7.5, sampleErrbacks), + 1, sampleErrbacks)) + .toBe(true); + expect(JN.equals( + JN._innards.integerNthRoot(3, 8.5, sampleErrbacks), + 2, sampleErrbacks)) + .toBe(true); + expect(function () { + JN._innards.integerNthRoot(3, -8, sampleErrbacks); + }) + .toThrowError(/radicand .* negative/); + expect(function () { + JN._innards.integerNthRoot(-3, 8, sampleErrbacks); + }) + .toThrowError(/root .* negative/); + + }); it("BigInteger methods", function() { @@ -356,13 +413,13 @@ R(["pyret-base/js/js-numbers"], function(JN) { // shd raise exception for arg outside [-1, +1] // but this is not testable via Pyret, because args are always sane // by the time this method is called - expect(function() { JN.makeBignum(-1.5).asin(sampleErrbacks); }).toThrowError(/domainError/); - expect(function() { JN.makeBignum(+1.5).asin(sampleErrbacks); }).toThrowError(/domainError/); + expect(function() { JN.makeBignum(-1.5).asin(sampleErrbacks); }).toThrowError(/out of domain/); + expect(function() { JN.makeBignum(+1.5).asin(sampleErrbacks); }).toThrowError(/out of domain/); // BigInteger.*acos // shd raise exc for arg < -1 or > 1 - expect(function() { JN.makeBignum(-1.5).acos(sampleErrbacks); }).toThrowError(/domainError/); - expect(function() { JN.makeBignum(+1.5).acos(sampleErrbacks); }).toThrowError(/domainError/); + expect(function() { JN.makeBignum(-1.5).acos(sampleErrbacks); }).toThrowError(/out of domain/); + expect(function() { JN.makeBignum(+1.5).acos(sampleErrbacks); }).toThrowError(/out of domain/); // BigInteger.*.atan // should work @@ -371,7 +428,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { // atan2 (perhaps Pyret test is enough) expect(function () { JN.atan2(JN.makeBignum(0), JN.makeBignum(0), sampleErrbacks); - }).toThrowError(/domainError/); + }).toThrowError(/out of domain/); // BigInteger.*.sin // should work @@ -385,12 +442,11 @@ R(["pyret-base/js/js-numbers"], function(JN) { // should work expect(JN.makeBignum(0).tan(sampleErrbacks)).toEqual(0); - // BigInteger.*.expt calls bnPow, which calls bnpExp // should raise exception for too-large expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrbacks); - }).toThrowError(/domainError/); + }).toThrowError(/exponent .* too large/); // BigInteger.*.log // should raise exception for arg <= 0 @@ -721,7 +777,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - }); jazz.execute(); From 8c85b1f65d7282ecd85fcefbb71bb64f90088c64 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Tue, 23 Sep 2025 16:00:51 -0400 Subject: [PATCH 15/36] - BigInteger canonicalizer bnpClamp() #1823 ** should remove fields at .t and above ** should remove any trailing zero fields, also reducing .t - bnDivide() should call clamp() --- src/js/base/js-numbers.js | 56 ++++++++++++++++++++++++++++---- tests/jsnums-test/jsnums-test.js | 7 ++-- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index e91254c26..a974852b0 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1202,20 +1202,20 @@ define("pyret-base/js/js-numbers", function() { // _integerMultiply: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerMultiply = makeIntegerBinop( function(m, n) { - // console.log('doing _integerMultiplyI', m, n); return m * n; }, function(m, n) { - // console.log('doing _integerMultiplyII[_,40]', m, n['40']); return bnMultiply.call(m, n); }); //_integerQuotient: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerQuotient = makeIntegerBinop( function(m, n) { + // console.log('_integerQuotientI', m, n); return ((m - (m % n))/ n); }, function(m, n) { + // console.log('_integerQuotientII', m, n); return bnDivide.call(m, n); }); @@ -1467,13 +1467,21 @@ define("pyret-base/js/js-numbers", function() { d = negate(d, errbacks); } + // console.log('initly n =', n, 'd =', d); + var divisor = _integerGcd(abs(n, errbacks), abs(d, errbacks)); + // console.log('divisor =', divisor); + // console.log('> find iq n'); n = _integerQuotient(n, divisor); + // console.log('iq n =', n); + // console.log('> find iq d'); d = _integerQuotient(d, divisor); + // console.log('iq d =', d); // Optimization: if we can get around construction the rational // in favor of just returning n, do it: if (_integerIsOne(d) || _integerIsZero(n)) { + // console.log('returning int', n, 'instead of rat'); return n; } @@ -2105,7 +2113,7 @@ define("pyret-base/js/js-numbers", function() { } exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0')); } - // console.log('exponent[40] =', exponent['40']); + // console.log('exponent =', exponent); var finalDen = denominatorTen; // console.log('calling _integerMultiply'); @@ -2115,6 +2123,7 @@ define("pyret-base/js/js-numbers", function() { finalNum = negate(finalNum, errbacks); } // console.log('finalNum2 =', finalNum); + // console.log('finalDen2 =', finalDen); // if (!equals(exponent, 1)) { if (exponentNegativeP) { @@ -2126,8 +2135,7 @@ define("pyret-base/js/js-numbers", function() { } } // console.log('finalNum3 =', finalNum); - // console.log('finalNum.40 =', finalNum['40']); - // console.log('finalDen =', finalDen); + // console.log('finalDen3 =', finalDen); return Rational.makeInstance(finalNum, finalDen, errbacks); } @@ -2570,8 +2578,23 @@ define("pyret-base/js/js-numbers", function() { // (protected) clamp off excess high words function bnpClamp() { + // console.log('bnpClamp', this); var c = this.s&this.DM; - while(this.t > 0 && this[this.t-1] == c) --this.t; + // console.log(' c =', c); + if (this.t > 0) { + var i = this.t; + while (this[i]) { + delete this[i]; + i++; + } + } + // if (this.t > 1 && this[this.t]) delete this[this.t]; + // this.t > 1? + while(this.t > 0 && this[this.t-1] == c) { + --this.t; + delete this[this.t]; + } + // console.log(' clamped return', this); } // (public) return string representation in given radix @@ -2760,22 +2783,27 @@ define("pyret-base/js/js-numbers", function() { // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m,q,r) { + // console.log('** bnpDivRemTo', this, m,q,r); var pm = m.abs(); if(pm.t <= 0) return; + // console.log('bdrt I'); var pt = this.abs(); if(pt.t < pm.t) { if(q != null) q.fromInt(0); if(r != null) this.copyTo(r); return; } + // console.log('bdrt II'); if(r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; + // console.log('ys=', ys); var y0 = y[ys-1]; if(y0 == 0) return; + // console.log('bdrt III'); var yt = y0*(1<1)?y[ys-2]>>this.F2:0); var d1 = this.FV/yt, d2 = (1< Date: Wed, 24 Sep 2025 01:40:12 -0400 Subject: [PATCH 16/36] - test that bignums indeed have unique representations - test: bnToString _integerIsZero _integerIsOne _integerGcd --- src/js/base/js-numbers.js | 4 +++ tests/jsnums-test/jsnums-test.js | 49 ++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index a974852b0..39d7e5885 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -4135,6 +4135,10 @@ define("pyret-base/js/js-numbers", function() { // the following exposes innards for testing Numbers['_innards'] = { + _integerGcd: _integerGcd, + _integerIsOne: _integerIsOne, + _integerIsZero: _integerIsZero, + bnToString: bnToString, integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, makeNumericBinop: makeNumericBinop, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index e021279cc..f38721673 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -110,6 +110,24 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.equals(JN.fromString("1e311", sampleErrbacks), JN.makeBignum("1e311"), sampleErrbacks)) .toBe(true); + // toEqual works too, because bigints have a unique respresentation, thanks to bnpClamp() + expect(JN.fromString("1e1", sampleErrbacks)) + .toEqual(JN.makeBignum('10')); + expect(JN.fromString("1e5", sampleErrbacks)) + .toEqual(JN.makeBignum('1e5')); + expect(JN.fromString("1e30", sampleErrbacks)) + .toEqual(JN.makeBignum('1e30')); + expect(JN.fromString("1e140", sampleErrbacks)) + .toEqual(JN.makeBignum('1e140')); + expect(JN.fromString("1e141", sampleErrbacks)) + .toEqual(JN.makeBignum('1e141')); + expect(JN.fromString("1e307", sampleErrbacks)) + .toEqual(JN.makeBignum('1e307')); + expect(JN.fromString("1e309", sampleErrbacks)) + .toEqual(JN.makeBignum('1e309')); + expect(JN.fromString("1e311", sampleErrbacks)) + .toEqual(JN.makeBignum('1e311')); + expect(JN.equals(JN.fromString("1/2", sampleErrbacks), JN.makeRational(1, 2, sampleErrbacks), sampleErrbacks)) @@ -340,6 +358,36 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); + it('bnToString', function() { + expect(JN._innards.bnToString.call(JN.makeBignum('1e8', sampleErrbacks), 10)) + .toEqual('100000000'); + expect(JN._innards.bnToString.call(JN.makeBignum('11259375', sampleErrbacks), 16)) + .toEqual('abcdef'); + expect(JN.makeBignum('1e8', sampleErrbacks).toString()) + .toEqual('100000000'); + + }); + + + it('_integer* functions', function() { + + expect(JN._innards._integerIsZero(0)).toBe(true); + expect(JN._innards._integerIsZero(1)).toBe(false); + expect(JN._innards._integerIsZero(JN.makeBignum(0, sampleErrbacks))).toBe(true); + expect(JN._innards._integerIsZero(JN.makeBignum(1, sampleErrbacks))).toBe(false); + + expect(JN._innards._integerIsOne(1)).toBe(true); + expect(JN._innards._integerIsOne(2)).toBe(false); + expect(JN._innards._integerIsOne(JN.makeBignum(1, sampleErrbacks))).toBe(true); + expect(JN._innards._integerIsOne(JN.makeBignum(2, sampleErrbacks))).toBe(false); + + expect(JN._innards._integerGcd(12, 18, sampleErrbacks)).toEqual(6); + expect(JN._innards._integerGcd(JN.makeBignum('12', sampleErrbacks), + JN.makeBignum('18', sampleErrbacks), sampleErrbacks)) + .toEqual(JN.makeBignum('6', sampleErrbacks)) + + }); + it("nthRoot integerNthRoot", function() { expect(JN.equals( JN._innards.nthRoot(3, 8, sampleErrbacks), @@ -399,6 +447,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); + it("BigInteger methods", function() { expect(JN.equals( From 8d75cd89e07767c4649e665916e1432488ad585b Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 24 Sep 2025 19:45:55 -0400 Subject: [PATCH 17/36] tests for - _integer{Is{Zero,NegativeOne},Modulo,DivideToFixnum,{Greater,Less}Than{,OrEqual}} - splitIntIntoMantissaExpt --- src/js/base/js-numbers.js | 15 ++++-- tests/jsnums-test/jsnums-test.js | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 39d7e5885..dab45b623 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1236,7 +1236,7 @@ define("pyret-base/js/js-numbers", function() { // to the left of the decimal point. // Because mantissa is a JS-double, there is in general a loss of precision. // splitIntIntoMantissaExpt is used to create a best-possible JS-double version - // of its argument arbitrarily precise integer. + // of an arbitrarily precise integer. // E.g., splitIntIntoMantissaExpt(256) returns // [2.56, 2] // splitIntIntoMantissaExpt(111222333444555666777888999) returns @@ -4135,9 +4135,18 @@ define("pyret-base/js/js-numbers", function() { // the following exposes innards for testing Numbers['_innards'] = { - _integerGcd: _integerGcd, - _integerIsOne: _integerIsOne, _integerIsZero: _integerIsZero, + _integerIsOne: _integerIsOne, + _integerIsNegativeOne: _integerIsNegativeOne, + _integerModulo: _integerModulo, + _integerGcd: _integerGcd, + _integerDivideToFixnum: _integerDivideToFixnum, + _integerEquals: _integerEquals, + _integerGreaterThan: _integerGreaterThan, + _integerLessThan: _integerLessThan, + _integerGreaterThanOrEqual: _integerGreaterThanOrEqual, + _integerLessThanOrEqual: _integerLessThanOrEqual, + splitIntIntoMantissaExpt: splitIntIntoMantissaExpt, bnToString: bnToString, integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index f38721673..e97bc62c7 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -381,11 +381,89 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN._innards._integerIsOne(JN.makeBignum(1, sampleErrbacks))).toBe(true); expect(JN._innards._integerIsOne(JN.makeBignum(2, sampleErrbacks))).toBe(false); + expect(JN._innards._integerIsNegativeOne(-1)).toBe(true); + expect(JN._innards._integerIsNegativeOne(1)).toBe(false); + expect(JN._innards._integerIsNegativeOne(JN.makeBignum(-1, sampleErrbacks))).toBe(true); + expect(JN._innards._integerIsNegativeOne(JN.makeBignum(1, sampleErrbacks))).toBe(false); + expect(JN._innards._integerGcd(12, 18, sampleErrbacks)).toEqual(6); expect(JN._innards._integerGcd(JN.makeBignum('12', sampleErrbacks), JN.makeBignum('18', sampleErrbacks), sampleErrbacks)) .toEqual(JN.makeBignum('6', sampleErrbacks)) + expect(JN._innards._integerModulo(12, 10, sampleErrbacks)).toEqual(2); + expect(JN._innards._integerModulo(JN.makeBignum('12', sampleErrbacks), + JN.makeBignum('10', sampleErrbacks), sampleErrbacks)) + .toEqual(JN.makeBignum('2', sampleErrbacks)) + + expect(JN._innards.splitIntIntoMantissaExpt('256')) + .toEqual([2.56, 2]); + expect(JN._innards.splitIntIntoMantissaExpt('111222333444555666777888999')) + .toEqual([1.1122233344455567, 26]); + + expect(JN._innards._integerDivideToFixnum(2, 3)) + .toEqual(2/3); + expect(JN._innards._integerDivideToFixnum(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(2/3); + + expect(JN._innards._integerEquals(2,2)) + .toEqual(true); + expect(JN._innards._integerEquals(JN.makeBignum('2e311'), + JN.makeBignum('2e311'))) + .toEqual(true); + expect(JN._innards._integerEquals(2,3)) + .toEqual(false); + expect(JN._innards._integerEquals(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(false); + + expect(JN._innards._integerGreaterThan(2,2)) + .toEqual(false); + expect(JN._innards._integerGreaterThan(JN.makeBignum('2e311'), + JN.makeBignum('2e311'))) + .toEqual(false); + expect(JN._innards._integerGreaterThan(2,3)) + .toEqual(false); + expect(JN._innards._integerGreaterThan(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(false); + + expect(JN._innards._integerLessThan(2,2)) + .toEqual(false); + expect(JN._innards._integerLessThan(JN.makeBignum('2e311'), + JN.makeBignum('2e311'))) + .toEqual(false); + expect(JN._innards._integerLessThan(2,3)) + .toEqual(true); + expect(JN._innards._integerLessThan(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(true); + + expect(JN._innards._integerGreaterThanOrEqual(2,2)) + .toEqual(true); + expect(JN._innards._integerGreaterThanOrEqual(JN.makeBignum('2e311'), + JN.makeBignum('2e311'))) + .toEqual(true); + expect(JN._innards._integerGreaterThanOrEqual(2,3)) + .toEqual(false); + expect(JN._innards._integerGreaterThanOrEqual(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(false); + + expect(JN._innards._integerLessThanOrEqual(2,2)) + .toEqual(true); + expect(JN._innards._integerLessThanOrEqual(JN.makeBignum('2e311'), + JN.makeBignum('2e311'))) + .toEqual(true); + expect(JN._innards._integerLessThanOrEqual(2,3)) + .toEqual(true); + expect(JN._innards._integerLessThanOrEqual(JN.makeBignum('2e311'), + JN.makeBignum('3e311'))) + .toEqual(true); + + + }); it("nthRoot integerNthRoot", function() { From 3b9cad42503e5621d3b33c5737b275f9b32bc0f2 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 25 Sep 2025 11:30:41 -0400 Subject: [PATCH 18/36] - test BigInteger's {copy,sub,multiply,{d,}{l,r}Shift}To - bnpD{L,R}ShiftTo() should canonicalize --- src/js/base/js-numbers.js | 3 ++ tests/jsnums-test/jsnums-test.js | 86 +++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index dab45b623..5967286e2 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2673,6 +2673,7 @@ define("pyret-base/js/js-numbers", function() { for(i = n-1; i >= 0; --i) r[i] = 0; r.t = this.t+n; r.s = this.s; + r.clamp(); } // (protected) r = this >> n*DB @@ -2680,6 +2681,7 @@ define("pyret-base/js/js-numbers", function() { for(var i = n; i < this.t; ++i) r[i-n] = this[i]; r.t = Math.max(this.t-n,0); r.s = this.s; + r.clamp(); } // (protected) r = this << n @@ -4147,6 +4149,7 @@ define("pyret-base/js/js-numbers", function() { _integerGreaterThanOrEqual: _integerGreaterThanOrEqual, _integerLessThanOrEqual: _integerLessThanOrEqual, splitIntIntoMantissaExpt: splitIntIntoMantissaExpt, + nbi: nbi, bnToString: bnToString, integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index e97bc62c7..1623024b6 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -368,6 +368,49 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); + it('bnp* functions', function() { + var b1, b2, r, expectedR; + + b1 = JN.makeBignum('9e311'); + r = JN._innards.nbi(); + b1.copyTo(r); + expect(r).toEqual(b1); + + b2 = JN.makeBignum('8e311'); + r = JN._innards.nbi(); + b1.subTo(b2, r); + expectedR = JN.makeBignum('1e311'); + expect(r).toEqual(expectedR); + + r = JN._innards.nbi(); + b1.multiplyTo(b2, r); + expectedR = JN.makeBignum('72e622'); + expect(r).toEqual(expectedR); + + b1 = JN.makeBignum(JN.expt(2,5)); + r = JN._innards.nbi(); + b1.dlShiftTo(1, r); + expectedR = JN.makeBignum(JN.expt(2,26 + 5)); + expect(r).toEqual(expectedR); + + b1 = JN.makeBignum(JN.expt(2,26 + 5)); + r = JN._innards.nbi(); + b1.drShiftTo(1, r); + expectedR = JN.makeBignum(JN.expt(2,5)); + expect(r).toEqual(expectedR); + + b1 = JN.makeBignum(JN.expt(2,5)); + r = JN._innards.nbi(); + b1.lShiftTo(1, r); + expectedR = JN.makeBignum(JN.expt(2,6)); + expect(r).toEqual(expectedR); + + r = JN._innards.nbi(); + b1.rShiftTo(1, r); + expectedR = JN.makeBignum(JN.expt(2,4)); + expect(r).toEqual(expectedR); + + }); it('_integer* functions', function() { @@ -462,8 +505,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.makeBignum('3e311'))) .toEqual(true); - - }); it("nthRoot integerNthRoot", function() { @@ -525,7 +566,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - it("BigInteger methods", function() { expect(JN.equals( @@ -781,6 +821,26 @@ R(["pyret-base/js/js-numbers"], function(JN) { .denominator(sampleErrbacks).toFixnum()) .toEqual(50); + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).floor(sampleErrbacks), + 3, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).ceiling(sampleErrbacks), + 4, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.14, sampleErrbacks).round(sampleErrbacks), + 3, sampleErrbacks)) + .toBe(true); + + expect(JN.equals( + JN.Roughnum.makeInstance(3.5, sampleErrbacks).roundEven(sampleErrbacks), + 4, sampleErrbacks)) + .toBe(true); + expect(JN.Roughnum.makeInstance(2.3, sampleErrbacks).greaterThan( JN.Roughnum.makeInstance(1.3, sampleErrbacks), sampleErrbacks)) .toBe(true); @@ -815,26 +875,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { 0.0001, sampleErrbacks)) .toBe(true); - expect(JN.equals( - JN.Roughnum.makeInstance(3.14, sampleErrbacks).floor(sampleErrbacks), - 3, sampleErrbacks)) - .toBe(true); - - expect(JN.equals( - JN.Roughnum.makeInstance(3.14, sampleErrbacks).ceiling(sampleErrbacks), - 4, sampleErrbacks)) - .toBe(true); - - expect(JN.equals( - JN.Roughnum.makeInstance(3.14, sampleErrbacks).round(sampleErrbacks), - 3, sampleErrbacks)) - .toBe(true); - - expect(JN.equals( - JN.Roughnum.makeInstance(3.5, sampleErrbacks).roundEven(sampleErrbacks), - 4, sampleErrbacks)) - .toBe(true); - expect(JN.roughlyEquals( JN.Roughnum.makeInstance(2.5, sampleErrbacks).log(sampleErrbacks), JN.fromFixnum(0.91629), From cb5b9486b27220d233898068e93755da899ca3d4 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 25 Sep 2025 14:36:12 -0400 Subject: [PATCH 19/36] test for bnp{Squareto,DivRemTo,Exp,IsEven,ModInt} --- src/js/base/js-numbers.js | 2 + tests/jsnums-test/jsnums-test.js | 68 ++++++++++++++++++++++++-------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 5967286e2..da7810228 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2958,6 +2958,7 @@ define("pyret-base/js/js-numbers", function() { // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e, z, errbacks) { + // console.log('bnpExp', this, e,z); if (greaterThan(e, 0xffffffff, errbacks)) { errbacks.throwDomainError('expt: exponent ' + e + ' too large'); } @@ -4137,6 +4138,7 @@ define("pyret-base/js/js-numbers", function() { // the following exposes innards for testing Numbers['_innards'] = { + NullExp: NullExp, _integerIsZero: _integerIsZero, _integerIsOne: _integerIsOne, _integerIsNegativeOne: _integerIsNegativeOne, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 1623024b6..66b84c4bb 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -368,48 +368,84 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - it('bnp* functions', function() { - var b1, b2, r, expectedR; + it('BigInteger bnp* methods', function() { + var n2r5, q, r, expectedR; - b1 = JN.makeBignum('9e311'); - r = JN._innards.nbi(); - b1.copyTo(r); - expect(r).toEqual(b1); + // bnpCopyTo + var n9e311 = JN.makeBignum('9e311'); + var r = JN._innards.nbi(); + n9e311.copyTo(r); + expect(r).toEqual(n9e311); - b2 = JN.makeBignum('8e311'); + // bnpSubTo + var n8e311 = JN.makeBignum('8e311'); r = JN._innards.nbi(); - b1.subTo(b2, r); + n9e311.subTo(n8e311, r); expectedR = JN.makeBignum('1e311'); expect(r).toEqual(expectedR); + // bnpMultiplyTo r = JN._innards.nbi(); - b1.multiplyTo(b2, r); + n9e311.multiplyTo(n8e311, r); expectedR = JN.makeBignum('72e622'); expect(r).toEqual(expectedR); - b1 = JN.makeBignum(JN.expt(2,5)); + // bnpSquareTo + r = JN._innards.nbi(); + n9e311.squareTo(r); + expectedR = JN.makeBignum('81e622'); + expect(r).toEqual(expectedR); + + // bnpDivRemTo + n2r5 = JN.makeBignum(JN.expt(2,5)); + var q = JN._innards.nbi(); + r = JN._innards.nbi(); + n2r5.divRemTo(JN.makeBignum(17), q,r); + var expectedQ = JN.makeBignum(1); + expectedR = JN.makeBignum(15); + expect(r).toEqual(expectedR); + expect(q).toEqual(expectedQ); + + // bnpModInt + expect(n2r5.modInt(17)).toEqual(15); + + // bnpIsEven + expect(n2r5.isEven()).toBe(true); + + // bnpDLShiftTo r = JN._innards.nbi(); - b1.dlShiftTo(1, r); + n2r5.dlShiftTo(1, r); expectedR = JN.makeBignum(JN.expt(2,26 + 5)); expect(r).toEqual(expectedR); - b1 = JN.makeBignum(JN.expt(2,26 + 5)); + // bnpDRShiftTo + var n2r31 = JN.makeBignum(JN.expt(2,26 + 5)); r = JN._innards.nbi(); - b1.drShiftTo(1, r); + n2r31.drShiftTo(1, r); expectedR = JN.makeBignum(JN.expt(2,5)); expect(r).toEqual(expectedR); - b1 = JN.makeBignum(JN.expt(2,5)); + // bnpLShiftTo r = JN._innards.nbi(); - b1.lShiftTo(1, r); + n2r5.lShiftTo(1, r); expectedR = JN.makeBignum(JN.expt(2,6)); expect(r).toEqual(expectedR); + // bnpRShiftTo r = JN._innards.nbi(); - b1.rShiftTo(1, r); + n2r5.rShiftTo(1, r); expectedR = JN.makeBignum(JN.expt(2,4)); expect(r).toEqual(expectedR); + // bnpExp + expect(n9e311.bnpExp(JN.makeBignum(2), new JN._innards.NullExp())) + .toEqual(JN.makeBignum('81e622')); + + expect(function() { + n9e311.bnpExp(JN.makeBignum(0xffffffff + 1), new JN._innards.NullExp(), sampleErrbacks); + }).toThrowError(/exponent .* too large/); + + }); it('_integer* functions', function() { From 15914a98ff02b01be4c3b3cd70f4a85a5420b643 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 26 Sep 2025 12:13:26 -0400 Subject: [PATCH 20/36] - makeInteger{UnOp,Binop}: use & propagate errbacks appropriately - test for: makeInteger{UnOp,Binop} - test for: bnpAddTo bnpDMultiply bnpMillerRabin - test for: bnRemainder bnModPow bnDivideAndRemainder bnIsProbablePrime --- src/js/base/js-numbers.js | 21 ++-- tests/jsnums-test/jsnums-test.js | 188 +++++++++++++++++++++++-------- 2 files changed, 151 insertions(+), 58 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index da7810228..fed3e791b 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1074,7 +1074,7 @@ define("pyret-base/js/js-numbers", function() { // common type before doing an operation. var makeIntegerBinop = function(onFixnums, onBignums, options) { options = options || {}; - return (function(m, n) { + return (function(m, n, errbacks) { if (m instanceof Rational) { m = numerator(m); } @@ -1084,7 +1084,7 @@ define("pyret-base/js/js-numbers", function() { } if (typeof(m) === 'number' && typeof(n) === 'number') { - var result = onFixnums(m, n); + var result = onFixnums(m, n, errbacks); if (! isOverflow(result) || (options.ignoreOverflow)) { return result; @@ -1092,7 +1092,7 @@ define("pyret-base/js/js-numbers", function() { } if (m instanceof Roughnum || n instanceof Roughnum) { return Roughnum.makeInstance( - onFixnums(toFixnum(m), toFixnum(n)), errbacks); + onFixnums(toFixnum(m), toFixnum(n), errbacks), errbacks); } if (typeof(m) === 'number') { m = makeBignum(m); @@ -1100,31 +1100,31 @@ define("pyret-base/js/js-numbers", function() { if (typeof(n) === 'number') { n = makeBignum(n); } - return onBignums(m, n); + return onBignums(m, n, errbacks); }); }; - var makeIntegerUnOp = function(onFixnums, onBignums, options, errbacks) { + var makeIntegerUnOp = function(onFixnums, onBignums, options) { options = options || {}; - return (function(m) { + return (function(m, errbacks) { if (m instanceof Rational) { m = numerator(m); } if (typeof(m) === 'number') { - var result = onFixnums(m); + var result = onFixnums(m, errbacks); if (! isOverflow(result) || (options.ignoreOverflow)) { return result; } } if (m instanceof Roughnum) { - return Roughnum.makeInstance(onFixnums(toFixnum(m)), errbacks); + return Roughnum.makeInstance(onFixnums(toFixnum(m), errbacks), errbacks); } if (typeof(m) === 'number') { m = makeBignum(m); } - return onBignums(m); + return onBignums(m, errbacks); }); }; @@ -4152,9 +4152,10 @@ define("pyret-base/js/js-numbers", function() { _integerLessThanOrEqual: _integerLessThanOrEqual, splitIntIntoMantissaExpt: splitIntIntoMantissaExpt, nbi: nbi, - bnToString: bnToString, integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, + makeIntegerUnOp: makeIntegerUnOp, + makeIntegerBinop: makeIntegerBinop, makeNumericBinop: makeNumericBinop, nthRoot: nthRoot, sign: sign, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 66b84c4bb..24c23a764 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -34,8 +34,61 @@ R(["pyret-base/js/js-numbers"], function(JN) { describe("check functions that don't allow testing via Pyret programs", function() { - it("makeNumericBinop", function() { - var bogusNumFun = JN._innards.makeNumericBinop( + it("make*opFun", function() { + + var bogusIntegerUnOpFun = JN._innards.makeIntegerUnOp( + function(x, errbacks) { + if (x <= 2) return 1; + errbacks.throwDomainError('makeIntegerUnOp first fail'); + }, + function(x, errbacks) { + if (JN.lessThanOrEqual(x, 2, errbacks)) return 4; + errbacks.throwDomainError('makeIntegerUnOp second fail'); + }, + { + ignoreOverflow: true, + } + ); + + expect(bogusIntegerUnOpFun(1, sampleErrbacks)).toEqual(1); + expect(function() { bogusIntegerUnOpFun(3, sampleErrbacks); }) + .toThrowError(/first fail/); + expect(bogusIntegerUnOpFun(JN.makeBignum(1), sampleErrbacks)).toEqual(4); + + var bogusIntegerBinopFun = JN._innards.makeIntegerBinop( + function(x, y, errbacks) { + if (x <= 2 && y <= 2) return 1; + if (x <= 2) return 2; + if (y <= 2) return 3; + errbacks.throwDomainError('makeIntegerBinop first fail'); + }, + function(x, y, errbacks) { + if (JN.lessThanOrEqual(x, 2, errbacks) && JN.lessThanOrEqual(y, 2, errbacks)) return 4; + if (JN.lessThanOrEqual(x, 2, errbacks)) return 5; + if (JN.lessThanOrEqual(y, 2, errbacks)) return 6; + errbacks.throwDomainError('makeIntegerBinop second fail'); + }, + { + isOverflow: true, + } + ); + + expect(bogusIntegerBinopFun(1, 1, sampleErrbacks)).toEqual(1); + expect(bogusIntegerBinopFun(1, 3, sampleErrbacks)).toEqual(2); + expect(bogusIntegerBinopFun(3, 1, sampleErrbacks)).toEqual(3); + expect(function() { bogusIntegerBinopFun(3, 3, sampleErrbacks); }).toThrowError(/first fail/); + + expect(bogusIntegerBinopFun(JN.makeBignum(1), JN.makeBignum(1), sampleErrbacks)). + toEqual(4); + expect(bogusIntegerBinopFun(JN.makeBignum(1), JN.makeBignum(3), sampleErrbacks)). + toEqual(5); + expect(bogusIntegerBinopFun(JN.makeBignum(3), JN.makeBignum(1), sampleErrbacks)). + toEqual(6); + expect(function() { + bogusIntegerBinopFun(JN.makeBignum(3), JN.makeBignum(3), sampleErrbacks); + }).toThrowError(/second fail/); + + var bogusNumericBinopFun = JN._innards.makeNumericBinop( function(x, y, errbacks) { if (x <= 2 && y <= 2) return 1; if (x <= 2) return 2; @@ -64,22 +117,22 @@ R(["pyret-base/js/js-numbers"], function(JN) { } ); - expect(bogusNumFun(1, 1, sampleErrbacks)).toEqual(1); - expect(bogusNumFun(1, 3, sampleErrbacks)).toEqual(2); - expect(bogusNumFun(3, 1, sampleErrbacks)).toEqual(3); - expect(function() { bogusNumFun(3, 3, sampleErrbacks); }).toThrowError(/first fail/); + expect(bogusNumericBinopFun(1, 1, sampleErrbacks)).toEqual(1); + expect(bogusNumericBinopFun(1, 3, sampleErrbacks)).toEqual(2); + expect(bogusNumericBinopFun(3, 1, sampleErrbacks)).toEqual(3); + expect(function() { bogusNumericBinopFun(3, 3, sampleErrbacks); }).toThrowError(/first fail/); - expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) + expect(bogusNumericBinopFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) .toEqual(4); - expect(bogusNumFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks)) + expect(bogusNumericBinopFun(JN.makeRational(3, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks)) .toEqual(5); - expect(bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) + expect(bogusNumericBinopFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(3, 2, sampleErrbacks), sampleErrbacks)) .toEqual(6); - expect(function() { bogusNumFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks); }) + expect(function() { bogusNumericBinopFun(JN.makeRational(5, 2, sampleErrbacks), JN.makeRational(5, 2, sampleErrbacks), sampleErrbacks); }) .toThrowError(/second fail/); - expect(bogusNumFun(-1, +1, sampleErrbacks)).toEqual(7); - expect(bogusNumFun(+1, -1, sampleErrbacks)).toEqual(8); + expect(bogusNumericBinopFun(-1, +1, sampleErrbacks)).toEqual(7); + expect(bogusNumericBinopFun(+1, -1, sampleErrbacks)).toEqual(8); }); @@ -89,11 +142,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { var bigIntStr = "1" + new Array(309 + 1).join("0"); // 1 followed by 309 0s expect(JN.fromString(bigIntStr, sampleErrbacks)).toEqual(JN.makeBignum(bigIntStr)); - // for sci-not and 'p/q', fromString() and makeBignum()/makeRational() can give - // structurally unequal but operationally equivalent results, so the following fails: - // expect(JN.fromString("1e141", sampleErrbacks)).toEqual(JN.makeBignum("1e141")); - // however you can refashion the test using JN.equals - expect(JN.equals(JN.fromString("1e1", sampleErrbacks), 10)).toBe(true); expect(JN.equals(JN.fromString("1e5", sampleErrbacks), JN.makeBignum("1e5"), sampleErrbacks)) .toBe(true); @@ -267,29 +315,29 @@ R(["pyret-base/js/js-numbers"], function(JN) { .toBe(true); expect(JN.equals( JN.toRational(JN.Roughnum.makeInstance(2.718, sampleErrbacks)), - JN.fromString('2.718'), + JN.fromString('2.718', sampleErrbacks), sampleErrbacks)) .toBe(true); // toRoughnum expect(JN.roughlyEquals( JN.toRoughnum(5, sampleErrbacks), - JN.fromString('~5'), + JN.fromString('~5', sampleErrbacks), 0.00001, sampleErrbacks)) .toBe(true); expect(JN.roughlyEquals( JN.toRoughnum(-5, sampleErrbacks), - JN.fromString('~-5'), + JN.fromString('~-5', sampleErrbacks), 0.00001, sampleErrbacks)) .toBe(true); expect(JN.roughlyEquals( JN.toRoughnum(0, sampleErrbacks), - JN.fromString('~0'), + JN.fromString('~0', sampleErrbacks), 0.00001, sampleErrbacks)) .toBe(true); expect(JN.roughlyEquals( JN.toRoughnum(3.14, sampleErrbacks), - JN.fromString('~3.14'), + JN.fromString('~3.14', sampleErrbacks), 0.00001, sampleErrbacks)) .toBe(true); expect(JN.roughlyEquals( @@ -358,18 +406,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); - it('bnToString', function() { - expect(JN._innards.bnToString.call(JN.makeBignum('1e8', sampleErrbacks), 10)) - .toEqual('100000000'); - expect(JN._innards.bnToString.call(JN.makeBignum('11259375', sampleErrbacks), 16)) - .toEqual('abcdef'); - expect(JN.makeBignum('1e8', sampleErrbacks).toString()) - .toEqual('100000000'); - - }); - it('BigInteger bnp* methods', function() { - var n2r5, q, r, expectedR; // bnpCopyTo var n9e311 = JN.makeBignum('9e311'); @@ -377,6 +414,13 @@ R(["pyret-base/js/js-numbers"], function(JN) { n9e311.copyTo(r); expect(r).toEqual(n9e311); + // bnpAddTo + var n1e311 = JN.makeBignum('1e311'); + r = JN._innards.nbi(); + n9e311.addTo(n1e311, r); + var expectedR = JN.makeBignum('1e312'); + expect(r).toEqual(expectedR); + // bnpSubTo var n8e311 = JN.makeBignum('8e311'); r = JN._innards.nbi(); @@ -397,7 +441,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(r).toEqual(expectedR); // bnpDivRemTo - n2r5 = JN.makeBignum(JN.expt(2,5)); + var n2r5 = JN.makeBignum(JN.expt(2,5)); var q = JN._innards.nbi(); r = JN._innards.nbi(); n2r5.divRemTo(JN.makeBignum(17), q,r); @@ -406,9 +450,20 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(r).toEqual(expectedR); expect(q).toEqual(expectedQ); + // bnpDMultiply; + var n2 = JN.makeBignum(2); + n2.dMultiply(JN.makeBignum(3)); + expect(n2).toEqual(JN.makeBignum(6)); + // bnpModInt expect(n2r5.modInt(17)).toEqual(15); + // bnpMillerRabin + expect(JN.makeBignum(31).millerRabin()).toBe(true); + expect(JN.makeBignum(32).millerRabin()).toBe(false); + expect(JN.makeBignum(100043).millerRabin()).toBe(true); + expect(JN.makeBignum(100051).millerRabin()).toBe(true); [sic] + // bnpIsEven expect(n2r5.isEven()).toBe(true); @@ -448,32 +503,67 @@ R(["pyret-base/js/js-numbers"], function(JN) { }); + it('BigInteger bn* functions', function() { + + var n2r5 = JN.makeBignum(Math.pow(2,5)); + + // bnSigNum + expect(n2r5.signum()).toEqual(1); + + // bnToString + expect(JN.makeBignum('1e8').toString(10)) + .toEqual('100000000'); + expect(JN.makeBignum('11259375').toString(16)) + .toEqual('abcdef'); + expect(JN.makeBignum('1e8').toString()) + .toEqual('100000000'); + + // bnRemainder + expect(JN.makeBignum(32).remainder(JN.makeBignum(17))) + .toEqual(JN.makeBignum(15)); + + // bnDivideAndRemainder + expect(JN.makeBignum(32).divideAndRemainder(JN.makeBignum(17))) + .toEqual([JN.makeBignum(1), JN.makeBignum(15)]); + + // bnModPow + expect(JN.makeBignum(2).modPow(JN.makeBignum(5), JN.makeBignum(15))) + .toEqual(JN.makeBignum(2)); + + // bnIsProbablePrime + expect(JN.makeBignum(31).isProbablePrime()).toBe(true); + expect(JN.makeBignum(32).isProbablePrime()).toBe(false); + expect(JN.makeBignum(100043).isProbablePrime()).toBe(true); + expect(JN.makeBignum(100051).isProbablePrime()).toBe(false); + + }); + it('_integer* functions', function() { expect(JN._innards._integerIsZero(0)).toBe(true); expect(JN._innards._integerIsZero(1)).toBe(false); - expect(JN._innards._integerIsZero(JN.makeBignum(0, sampleErrbacks))).toBe(true); - expect(JN._innards._integerIsZero(JN.makeBignum(1, sampleErrbacks))).toBe(false); + expect(JN._innards._integerIsZero(JN.makeBignum(0))).toBe(true); + expect(JN._innards._integerIsZero(JN.makeBignum(1))).toBe(false); expect(JN._innards._integerIsOne(1)).toBe(true); expect(JN._innards._integerIsOne(2)).toBe(false); - expect(JN._innards._integerIsOne(JN.makeBignum(1, sampleErrbacks))).toBe(true); - expect(JN._innards._integerIsOne(JN.makeBignum(2, sampleErrbacks))).toBe(false); + expect(JN._innards._integerIsOne(JN.makeBignum(1))).toBe(true); + expect(JN._innards._integerIsOne(JN.makeBignum(2))).toBe(false); expect(JN._innards._integerIsNegativeOne(-1)).toBe(true); expect(JN._innards._integerIsNegativeOne(1)).toBe(false); - expect(JN._innards._integerIsNegativeOne(JN.makeBignum(-1, sampleErrbacks))).toBe(true); - expect(JN._innards._integerIsNegativeOne(JN.makeBignum(1, sampleErrbacks))).toBe(false); + expect(JN._innards._integerIsNegativeOne(JN.makeBignum(-1))).toBe(true); + expect(JN._innards._integerIsNegativeOne(JN.makeBignum(1))).toBe(false); expect(JN._innards._integerGcd(12, 18, sampleErrbacks)).toEqual(6); - expect(JN._innards._integerGcd(JN.makeBignum('12', sampleErrbacks), - JN.makeBignum('18', sampleErrbacks), sampleErrbacks)) - .toEqual(JN.makeBignum('6', sampleErrbacks)) + expect(JN._innards._integerGcd(JN.makeBignum(12), + JN.makeBignum(18), sampleErrbacks)) + .toEqual(JN.makeBignum(6)) expect(JN._innards._integerModulo(12, 10, sampleErrbacks)).toEqual(2); - expect(JN._innards._integerModulo(JN.makeBignum('12', sampleErrbacks), - JN.makeBignum('10', sampleErrbacks), sampleErrbacks)) - .toEqual(JN.makeBignum('2', sampleErrbacks)) + expect(JN._innards._integerModulo(JN.makeBignum(12), + JN.makeBignum(10), sampleErrbacks)) + .toEqual(JN.makeBignum(2)) expect(JN._innards.splitIntIntoMantissaExpt('256')) .toEqual([2.56, 2]); @@ -671,7 +761,8 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.equals(JN.Rational.makeInstance(1, -1, sampleErrbacks), -1)).toBe(true); expect(JN.equals(JN.Rational.makeInstance(2, 1, sampleErrbacks), 2)).toBe(true); expect(JN.equals(JN.Rational.makeInstance(0, 1, sampleErrbacks), 0)).toBe(true); - expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks), JN.fromString("2/3"))) + expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks), + JN.fromString("2/3", sampleErrbacks))) .toBe(true); expect(JN.Rational.makeInstance(1, 3, sampleErrbacks).equals( @@ -692,7 +783,8 @@ R(["pyret-base/js/js-numbers"], function(JN) { .toBe(true); expect(JN.equals(JN.Rational.makeInstance(-4, 3, sampleErrbacks).negate(sampleErrbacks), - JN.fromString("4/3"), sampleErrbacks)) + JN.fromString("4/3", sampleErrbacks), + sampleErrbacks)) .toBe(true); expect(JN.equals(JN.Rational.makeInstance(2, 3, sampleErrbacks).multiply( From 09a47af341f4587840e59d0d63104e4b20565ba4 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 29 Sep 2025 09:07:48 -0400 Subject: [PATCH 21/36] test bnpToRadix --- tests/jsnums-test/jsnums-test.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 24c23a764..648916690 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -462,7 +462,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.makeBignum(31).millerRabin()).toBe(true); expect(JN.makeBignum(32).millerRabin()).toBe(false); expect(JN.makeBignum(100043).millerRabin()).toBe(true); - expect(JN.makeBignum(100051).millerRabin()).toBe(true); [sic] + expect(JN.makeBignum(100051).millerRabin()).toBe(true); // [sic] // bnpIsEven expect(n2r5.isEven()).toBe(true); @@ -501,6 +501,17 @@ R(["pyret-base/js/js-numbers"], function(JN) { }).toThrowError(/exponent .* too large/); + // bnpToRadix + expect(JN.makeBignum('1e8').toRadix()) + .toEqual('100000000'); + expect(JN.makeBignum('1e8').toRadix(10)) + .toEqual('100000000'); + expect(JN.makeBignum('11259375').toRadix(16)) + .toEqual('abcdef'); + expect(JN.makeBignum('1e8').toRadix()) + .toEqual('100000000'); + + }); it('BigInteger bn* functions', function() { @@ -511,6 +522,8 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(n2r5.signum()).toEqual(1); // bnToString + expect(JN.makeBignum('1e8').toString()) + .toEqual('100000000'); expect(JN.makeBignum('1e8').toString(10)) .toEqual('100000000'); expect(JN.makeBignum('11259375').toString(16)) From d727abd3cfbceac94fbc17ba0e4760ed426503ec Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 29 Sep 2025 10:33:23 -0400 Subject: [PATCH 22/36] - ensure all calls to equals(), lessThan{,OrEqual}() take errbacks - toRational() methods take errbacks param even tho they don't use them --- src/js/base/js-numbers.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index fed3e791b..3f3aaf7d4 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -588,7 +588,7 @@ define("pyret-base/js/js-numbers", function() { return x <= y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.lessThanOrEqual(y); + return x.lessThanOrEqual(y, errbacks); })(x, y, errbacks); }; @@ -608,7 +608,7 @@ define("pyret-base/js/js-numbers", function() { return x < y; } return makeNumericBinop(undefined, function(x, y, errbacks) { - return x.lessThan(y); + return x.lessThan(y, errbacks); })(x, y, errbacks); }; @@ -1570,7 +1570,7 @@ define("pyret-base/js/js-numbers", function() { _integerMultiply(this.d, other.n), errbacks); }; - Rational.prototype.toRational = function() { + Rational.prototype.toRational = function(errbacks) { return this; }; @@ -1622,8 +1622,8 @@ define("pyret-base/js/js-numbers", function() { var newN = sqrt(this.n); var newD = sqrt(this.d); if (isRational(newN) && isRational(newD) && - equals(floor(newN), newN) && - equals(floor(newD), newD)) { + equals(floor(newN), newN, errbacks) && + equals(floor(newD), newD, errbacks)) { return Rational.makeInstance(newN, newD, errbacks); } else { return divide(newN, newD, errbacks); @@ -1654,7 +1654,7 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.round = function(errbacks) { - var halfintp = equals(this.d, 2); + var halfintp = equals(this.d, 2, errbacks); var negativep = _integerLessThan(this.n, 0); var n = this.n; if (negativep) { @@ -2125,7 +2125,7 @@ define("pyret-base/js/js-numbers", function() { // console.log('finalNum2 =', finalNum); // console.log('finalDen2 =', finalDen); // - if (!equals(exponent, 1)) { + if (!equals(exponent, 1, errbacks)) { if (exponentNegativeP) { finalDen = _integerMultiply(finalDen, exponent); // finalDen = canonicalizeBignum(finalDen); @@ -3742,7 +3742,7 @@ define("pyret-base/js/js-numbers", function() { return this.compareTo(BigInteger.ZERO) <= 0; }; - BigInteger.prototype.toRational = function() { + BigInteger.prototype.toRational = function(errbacks) { return this; }; @@ -3969,7 +3969,7 @@ define("pyret-base/js/js-numbers", function() { multiply(r, 10, errbacks), d, errbacks); repeatingDigits.push(nextDigit.toString()); - if (equals(nextRemainder, firstRepeatingRemainder)) { + if (equals(nextRemainder, firstRepeatingRemainder, errbacks)) { break; } else { r = nextRemainder; @@ -4011,7 +4011,7 @@ define("pyret-base/js/js-numbers", function() { if (lessThan(d, 0, errbacks)) { errbacks.throwDomainError('toRepeatingDecimal: d < 0'); } - var sign = (lessThan(n, 0) ? "-" : ""); + var sign = (lessThan(n, 0, errbacks) ? "-" : ""); n = abs(n, errbacks); var beforeDecimalPoint = sign + quotient(n, d, errbacks); var afterDecimals = getResidue(remainder(n, d, errbacks), d, limit, errbacks); From 0bdd4994b748e858e5aca6b8a527ab3047bf1349 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 29 Sep 2025 10:42:49 -0400 Subject: [PATCH 23/36] test toRepeatingDecimal() for non-valid args --- tests/jsnums-test/jsnums-test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 648916690..5ecc8b8ec 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -359,9 +359,21 @@ R(["pyret-base/js/js-numbers"], function(JN) { it('other subrs', function() { // toRepeatingDecimal - expect(arrayEquals(JN.toRepeatingDecimal(883, 700), ['1', '26', '142857'], + expect(arrayEquals(JN.toRepeatingDecimal(883, 700, undefined, sampleErrbacks), ['1', '26', '142857'], undefined, sampleErrbacks)) .toBe(true); + expect(function() { + JN.toRepeatingDecimal(355/113, 10, undefined, sampleErrbacks); + }).toThrowError(/not an integer/); + expect(function() { + JN.toRepeatingDecimal(10, 113/355, undefined, sampleErrbacks); + }).toThrowError(/not an integer/); + expect(function() { + JN.toRepeatingDecimal(JN.makeRational(355, 113), 10, undefined, sampleErrbacks); + }).toThrowError(/not an integer/); + expect(function() { + JN.toRepeatingDecimal(10, JN.makeRational(113, 355), undefined, sampleErrbacks); + }).toThrowError(/not an integer/); // toStringDigits expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) From 00b90b9e9d6d7f7a12dde469a8f4ef84ef03ed6e Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 3 Oct 2025 10:26:02 -0400 Subject: [PATCH 24/36] - add num-gcd courtesy @blerner #1427 - runtime.js: gcd, lcm simplified as variable num args unneeded - add num-lcm also - image-lib.js: use simplified gcd - tests for num-gcd, num-lcm --- src/arr/compiler/compile-structs.arr | 2 ++ src/js/base/js-numbers.js | 50 +++++++++++++--------------- src/js/base/runtime.js | 16 +++++++++ src/js/trove/global.js | 2 ++ src/js/trove/image-lib.js | 2 +- tests/jsnums-test/jsnums-test.js | 6 ++-- tests/pyret/tests/test-numbers.arr | 8 +++++ 7 files changed, 55 insertions(+), 31 deletions(-) diff --git a/src/arr/compiler/compile-structs.arr b/src/arr/compiler/compile-structs.arr index 563a1e010..44706823f 100644 --- a/src/arr/compiler/compile-structs.arr +++ b/src/arr/compiler/compile-structs.arr @@ -3119,6 +3119,8 @@ runtime-provides = provides("builtin://global", "num-atan", t-number-unop, "num-atan2", t-number-binop, "num-modulo", t-number-binop, + "num-gcd", t-number-binop, + "num-lcm", t-number-binop, "num-remainder", t-number-binop, "num-sqrt", t-number-unop, "num-sqr", t-number-unop, diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 3f3aaf7d4..99254f2fd 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -942,47 +942,43 @@ define("pyret-base/js/js-numbers", function() { return x.integerSqrt(errbacks); }; - // gcd: pyretnum [pyretnum ...] -> pyretnum - var gcd = function(first, rest, errbacks) { + // gcd: pyretnum pyretnum -> pyretnum + var gcd = function(first, second, errbacks) { if (! isInteger(first)) { errbacks.throwDomainError('gcd: the argument ' + first.toString() + " is not an integer.", first); } - var a = abs(first, errbacks), t, b; - for(var i = 0; i < rest.length; i++) { - b = abs(rest[i], errbacks); - if (! isInteger(b)) { - errbacks.throwDomainError('gcd: the argument ' + b.toString() + - " is not an integer.", b); - } - while (! _integerIsZero(b)) { - t = a; - a = b; - b = _integerModulo(t, b); - } + if (! isInteger(second)) { + errbacks.throwDomainError('gcd: the argument ' + second.toString() + + " is not an integer.", second); + } + var a = abs(first, errbacks), t; + var b = abs(second, errbacks); + while (! _integerIsZero(b)) { + t = a; + a = b; + b = _integerModulo(t, b); } return a; }; - // lcm: pyretnum [pyretnum ...] -> pyretnum - var lcm = function(first, rest, errbacks) { + // lcm: pyretnum pyretnum -> pyretnum + var lcm = function(first, second, errbacks) { if (! isInteger(first)) { errbacks.throwDomainError('lcm: the argument ' + first.toString() + " is not an integer.", first); } + if (! isInteger(second)) { + errbacks.throwDomainError('lcm: the argument ' + second.toString() + + " is not an integer.", second); + } var result = abs(first, errbacks); if (_integerIsZero(result)) { return 0; } - for (var i = 0; i < rest.length; i++) { - if (! isInteger(rest[i])) { - errbacks.throwDomainError('lcm: the argument ' + rest[i].toString() + - " is not an integer.", rest[i]); - } - var divisor = _integerGcd(result, rest[i]); - if (_integerIsZero(divisor)) { - return 0; - } - result = divide(multiply(result, rest[i], errbacks), divisor, errbacks); + var divisor = _integerGcd(result, second); + if (_integerIsZero(divisor)) { + return 0; } + result = divide(multiply(result, second, errbacks), divisor, errbacks); return result; }; @@ -1004,7 +1000,7 @@ define("pyret-base/js/js-numbers", function() { } else if (isRational(x) && isRational(y)) { var xn = numerator(x, errbacks); var xd = denominator(x, errbacks); var yn = numerator(y, errbacks); var yd = denominator(y, errbacks); - var new_d = lcm(xd, [yd], errbacks); + var new_d = lcm(xd, yd, errbacks); var new_xn = multiply(xn, divide(new_d, xd, errbacks), errbacks); var new_yn = multiply(yn, divide(new_d, yd, errbacks), errbacks); return divide(remainder(new_xn, new_yn, errbacks), new_d, errbacks); diff --git a/src/js/base/runtime.js b/src/js/base/runtime.js index b540d206b..fe2b8bc4b 100644 --- a/src/js/base/runtime.js +++ b/src/js/base/runtime.js @@ -5224,6 +5224,20 @@ function (Namespace, jsnums, codePoint, util, exnStackParser, loader, seedrandom return thisRuntime.makeNumberBig(jsnums.remainder(n, m, NumberErrbacks)); } + var num_gcd = function(n, m) { + if (arguments.length !== 2) { var $a=new Array(arguments.length); for (var $i=0;$i Date: Mon, 6 Oct 2025 21:39:27 -0400 Subject: [PATCH 25/36] remove all (commented) debugging console.log's --- src/js/base/js-numbers.js | 48 --------------------------------------- 1 file changed, 48 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 99254f2fd..24cfd566e 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1207,11 +1207,9 @@ define("pyret-base/js/js-numbers", function() { //_integerQuotient: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerQuotient = makeIntegerBinop( function(m, n) { - // console.log('_integerQuotientI', m, n); return ((m - (m % n))/ n); }, function(m, n) { - // console.log('_integerQuotientII', m, n); return bnDivide.call(m, n); }); @@ -1452,7 +1450,6 @@ define("pyret-base/js/js-numbers", function() { }; Rational.makeInstance = function(n, d, errbacks) { - // console.log('doing rat.makeinst of', n, d); if (n === undefined) errbacks.throwUndefinedValue("n undefined", n, d); @@ -1463,21 +1460,13 @@ define("pyret-base/js/js-numbers", function() { d = negate(d, errbacks); } - // console.log('initly n =', n, 'd =', d); - var divisor = _integerGcd(abs(n, errbacks), abs(d, errbacks)); - // console.log('divisor =', divisor); - // console.log('> find iq n'); n = _integerQuotient(n, divisor); - // console.log('iq n =', n); - // console.log('> find iq d'); d = _integerQuotient(d, divisor); - // console.log('iq d =', d); // Optimization: if we can get around construction the rational // in favor of just returning n, do it: if (_integerIsOne(d) || _integerIsZero(n)) { - // console.log('returning int', n, 'instead of rat'); return n; } @@ -2057,7 +2046,6 @@ define("pyret-base/js/js-numbers", function() { // fromString: string -> (pyretnum | false) var fromString = function(x, errbacks) { - // console.log('doing fromString', x); if (x.match(digitRegexp)) { var n = Number(x); if (isOverflow(n)) { @@ -2082,7 +2070,6 @@ define("pyret-base/js/js-numbers", function() { if (beforeDecimalString !== '') { beforeDecimal = makeBignum(beforeDecimalString); } - // console.log('beforeDecimal =', beforeDecimal); // var afterDecimalString = aMatch[3]; var denominatorTen = 1; @@ -2094,8 +2081,6 @@ define("pyret-base/js/js-numbers", function() { afterDecimal = makeBignum(afterDecimalString); } } - // console.log('afterDecimal =', afterDecimal); - // console.log('denominatorTen =', denominatorTen); // var exponentString = aMatch[4]; var exponentNegativeP = false; @@ -2109,29 +2094,20 @@ define("pyret-base/js/js-numbers", function() { } exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0')); } - // console.log('exponent =', exponent); var finalDen = denominatorTen; - // console.log('calling _integerMultiply'); var finalNum = _integerAdd(_integerMultiply(beforeDecimal, denominatorTen), afterDecimal); - // console.log('finalNum1 =', finalNum); if (negativeP) { finalNum = negate(finalNum, errbacks); } - // console.log('finalNum2 =', finalNum); - // console.log('finalDen2 =', finalDen); // if (!equals(exponent, 1, errbacks)) { if (exponentNegativeP) { finalDen = _integerMultiply(finalDen, exponent); - // finalDen = canonicalizeBignum(finalDen); } else { finalNum = _integerMultiply(finalNum, exponent); - // finalNum = canonicalizeBignum(finalNum); } } - // console.log('finalNum3 =', finalNum); - // console.log('finalDen3 =', finalDen); return Rational.makeInstance(finalNum, finalDen, errbacks); } @@ -2420,7 +2396,6 @@ define("pyret-base/js/js-numbers", function() { // (public) Constructor function BigInteger(a,b,c) { - // console.log('doing BigInteger of', a, '(', a? a.length : 'x', ')', b,c); if(a != null) if("number" == typeof a) this.fromNumber(a,b,c); else if(b == null && "string" != typeof a) this.fromString(a,256); @@ -2574,9 +2549,7 @@ define("pyret-base/js/js-numbers", function() { // (protected) clamp off excess high words function bnpClamp() { - // console.log('bnpClamp', this); var c = this.s&this.DM; - // console.log(' c =', c); if (this.t > 0) { var i = this.t; while (this[i]) { @@ -2590,7 +2563,6 @@ define("pyret-base/js/js-numbers", function() { --this.t; delete this[this.t]; } - // console.log(' clamped return', this); } // (public) return string representation in given radix @@ -2781,27 +2753,22 @@ define("pyret-base/js/js-numbers", function() { // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m,q,r) { - // console.log('** bnpDivRemTo', this, m,q,r); var pm = m.abs(); if(pm.t <= 0) return; - // console.log('bdrt I'); var pt = this.abs(); if(pt.t < pm.t) { if(q != null) q.fromInt(0); if(r != null) this.copyTo(r); return; } - // console.log('bdrt II'); if(r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; - // console.log('ys=', ys); var y0 = y[ys-1]; if(y0 == 0) return; - // console.log('bdrt III'); var yt = y0*(1<1)?y[ys-2]>>this.F2:0); var d1 = this.FV/yt, d2 = (1< BigInteger var makeBignum = function(s) { - // console.log('doing makeBignum', s); if (typeof(s) === 'number') { s = s + ''; } s = expandExponent(s); - // console.log('s became', s, 'of length', s.length); return new BigInteger(s, 10); }; From 1ec7a95458bbb44692b8d961d8b4c06f46602dec Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Tue, 7 Oct 2025 10:34:19 -0400 Subject: [PATCH 26/36] jsnums-test.js: Add test for toRepeatingDecimal() mistakenly called with errbacks as 3rd (rather than 4th) arg --- tests/jsnums-test/jsnums-test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 45fce7737..c01b9ce2d 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -375,6 +375,11 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.toRepeatingDecimal(10, JN.makeRational(113, 355), undefined, sampleErrbacks); }).toThrowError(/not an integer/); + // errbacks given as 3rd rather than 4th arg, as happened once on cpo + expect(function() { + JN.toRepeatingDecimal(355/133, 10, sampleErrbacks); + }).toThrowError(/undefined/); + // toStringDigits expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) .toBe("123456789.00000"); From 7b63e0f0d95cee6e086443690a946e3fc73a1b51 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 8 Oct 2025 12:23:42 -0400 Subject: [PATCH 27/36] - lift def of getReside() up from toRepeatingDecimal() - document them both better - test for both for limit < size(repeating-decimal) --- src/js/base/js-numbers.js | 125 +++++++++++++++++-------------- tests/jsnums-test/jsnums-test.js | 12 ++- 2 files changed, 80 insertions(+), 57 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 24cfd566e..cf42fc838 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -3871,6 +3871,69 @@ define("pyret-base/js/js-numbers", function() { return asin(this.toFixnum(), errbacks); }; + ////////////////////////////////////////////////////////////////////// + // getResidue: integer, integer, integer -> [string, string] + // + // Given the numerator and denominator of a proper (<= 1) fraction, + // returns two strings constituting its repeating-decimal representation, + // where the first string is the non-repeating digits immediately after the + // decimal point, and the second string is the repeating digits thereafter. + // The third argument is the limit on the size of the repeating digits. + // If exceeded, the second string is `...`. + var getResidue = function(r, d, limit, errbacks) { + var digits = []; + var seenRemainders = {}; + seenRemainders[r] = true; + while(true) { + if (limit-- <= 0) { + return [digits.join(''), '...'] + } + + var nextDigit = quotient( + multiply(r, 10, errbacks), d, errbacks); + var nextRemainder = remainder( + multiply(r, 10, errbacks), + d, errbacks); + digits.push(nextDigit.toString()); + if (seenRemainders[nextRemainder]) { + r = nextRemainder; + break; + } else { + seenRemainders[nextRemainder] = true; + r = nextRemainder; + } + } + + var firstRepeatingRemainder = r; + var repeatingDigits = []; + while (true) { + var nextDigit = quotient(multiply(r, 10, errbacks), d, errbacks); + var nextRemainder = remainder( + multiply(r, 10, errbacks), + d, errbacks); + repeatingDigits.push(nextDigit.toString()); + if (equals(nextRemainder, firstRepeatingRemainder, errbacks)) { + break; + } else { + r = nextRemainder; + } + }; + + var digitString = digits.join(''); + var repeatingDigitString = repeatingDigits.join(''); + + while (digitString.length >= repeatingDigitString.length && + (digitString.substring( + digitString.length - repeatingDigitString.length) + === repeatingDigitString)) { + digitString = digitString.substring( + 0, digitString.length - repeatingDigitString.length); + } + + return [digitString, repeatingDigitString]; + + }; + ////////////////////////////////////////////////////////////////////// // toRepeatingDecimal: jsnum jsnum {limit: number}? -> [string, string, string] // @@ -3880,64 +3943,13 @@ define("pyret-base/js/js-numbers", function() { // non-repeating digits after the decimal, and the third are the // remaining repeating decimals. // - // An optional limit on the decimal expansion can be provided, in which - // case the search cuts off if we go past the limit. - // If this happens, the third argument returned becomes '...' to indicate + // An optional limit on the decimal expansion can be provided via + // a `limit` field of an object supplied as a third argument. This + // cuts off the search if we go past the limit. + // If this happens, the third string returned becomes '...' to indicate // that the search was prematurely cut off. + // The default limit is 512. var toRepeatingDecimal = (function() { - var getResidue = function(r, d, limit, errbacks) { - var digits = []; - var seenRemainders = {}; - seenRemainders[r] = true; - while(true) { - if (limit-- <= 0) { - return [digits.join(''), '...'] - } - - var nextDigit = quotient( - multiply(r, 10, errbacks), d, errbacks); - var nextRemainder = remainder( - multiply(r, 10, errbacks), - d, errbacks); - digits.push(nextDigit.toString()); - if (seenRemainders[nextRemainder]) { - r = nextRemainder; - break; - } else { - seenRemainders[nextRemainder] = true; - r = nextRemainder; - } - } - - var firstRepeatingRemainder = r; - var repeatingDigits = []; - while (true) { - var nextDigit = quotient(multiply(r, 10, errbacks), d, errbacks); - var nextRemainder = remainder( - multiply(r, 10, errbacks), - d, errbacks); - repeatingDigits.push(nextDigit.toString()); - if (equals(nextRemainder, firstRepeatingRemainder, errbacks)) { - break; - } else { - r = nextRemainder; - } - }; - - var digitString = digits.join(''); - var repeatingDigitString = repeatingDigits.join(''); - - while (digitString.length >= repeatingDigitString.length && - (digitString.substring( - digitString.length - repeatingDigitString.length) - === repeatingDigitString)) { - digitString = digitString.substring( - 0, digitString.length - repeatingDigitString.length); - } - - return [digitString, repeatingDigitString]; - - }; return function(n, d, options, errbacks) { // default limit on decimal expansion; can be overridden @@ -4099,6 +4111,7 @@ define("pyret-base/js/js-numbers", function() { _integerGreaterThanOrEqual: _integerGreaterThanOrEqual, _integerLessThanOrEqual: _integerLessThanOrEqual, splitIntIntoMantissaExpt: splitIntIntoMantissaExpt, + getResidue: getResidue, nbi: nbi, integerNthRoot: integerNthRoot, liftFixnumInteger: liftFixnumInteger, diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index c01b9ce2d..8452be73b 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -359,7 +359,12 @@ R(["pyret-base/js/js-numbers"], function(JN) { it('other subrs', function() { // toRepeatingDecimal - expect(arrayEquals(JN.toRepeatingDecimal(883, 700, undefined, sampleErrbacks), ['1', '26', '142857'], + expect(arrayEquals(JN.toRepeatingDecimal(883, 700, undefined, sampleErrbacks), + ['1', '26', '142857'], + undefined, sampleErrbacks)) + .toBe(true); + expect(arrayEquals(JN.toRepeatingDecimal(883, 700, {limit: 2}, sampleErrbacks), + ['1', '26', '...'], undefined, sampleErrbacks)) .toBe(true); expect(function() { @@ -380,6 +385,11 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.toRepeatingDecimal(355/133, 10, sampleErrbacks); }).toThrowError(/undefined/); + expect(arrayEquals(JN._innards.getResidue(183, 700, 512, sampleErrbacks), ['26', '142857'])) + .toBe(true); + expect(arrayEquals(JN._innards.getResidue(183, 700, 2, sampleErrbacks), ['26', '...'])) + .toBe(true); + // toStringDigits expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) .toBe("123456789.00000"); From 2a22911ee3f0962092a2b3c66b065e8796affbb9 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 9 Oct 2025 14:04:13 -0400 Subject: [PATCH 28/36] simplify toRepeatingDecimal(). Now that getResidue() is out, toRepeatingDecimal() doesn't need to create a function and return it. It can be the function directly. --- src/js/base/js-numbers.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index cf42fc838..ac0b14f14 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -3949,9 +3949,7 @@ define("pyret-base/js/js-numbers", function() { // If this happens, the third string returned becomes '...' to indicate // that the search was prematurely cut off. // The default limit is 512. - var toRepeatingDecimal = (function() { - - return function(n, d, options, errbacks) { + var toRepeatingDecimal = function(n, d, options, errbacks) { // default limit on decimal expansion; can be overridden var limit = 512; if (options && typeof(options.limit) !== 'undefined') { @@ -3976,8 +3974,7 @@ define("pyret-base/js/js-numbers", function() { var beforeDecimalPoint = sign + quotient(n, d, errbacks); var afterDecimals = getResidue(remainder(n, d, errbacks), d, limit, errbacks); return [beforeDecimalPoint].concat(afterDecimals); - }; - })(); + }; ////////////////////////////////////////////////////////////////////// // toStringDigits: jsnum jsnum -> string // Converts the number to a string, providing digits precision in the From d58e8389df40020c901c7ee61a02ca7aa5e2ae27 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 9 Oct 2025 15:31:20 -0400 Subject: [PATCH 29/36] jsnums-test.js: test toRepeatingDecimal errors, with and without correct errbacks arg --- tests/jsnums-test/jsnums-test.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 8452be73b..2ae19a201 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -384,6 +384,23 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(function() { JN.toRepeatingDecimal(355/133, 10, sampleErrbacks); }).toThrowError(/undefined/); + expect(function() { + JN.toRepeatingDecimal(10, 355/133, sampleErrbacks); + }).toThrowError(/undefined/); + expect(function() { + JN.toRepeatingDecimal(944473296573929, JN.makeBignum(0), sampleErrbacks); + }).toThrowError(/undefined/); + + // above 3 tests, with correct errbacks arg + expect(function() { + JN.toRepeatingDecimal(355/133, 10, undefined, sampleErrbacks); + }).toThrowError(/is not an integer/); + expect(function() { + JN.toRepeatingDecimal(10, 355/133, undefined, sampleErrbacks); + }).toThrowError(/is not an integer/); + expect(function() { + JN.toRepeatingDecimal(944473296573929, JN.makeBignum(0), undefined, sampleErrbacks); + }).toThrowError(/d equals 0/); expect(arrayEquals(JN._innards.getResidue(183, 700, 512, sampleErrbacks), ['26', '142857'])) .toBe(true); From 298e87cff259d72368b6f2e477592874e26288e4 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sun, 12 Oct 2025 22:48:13 -0400 Subject: [PATCH 30/36] Following's defs & calls need explicit errbacks propagration: toFixnum add subtract divide isInteger _integerIsOne _integerIsZero _integerIsNegativeOne _integerModulo _integerGcd _integerQuotient _integerRemainder _integerLessThan _integerGreaterThan _integerLessThanOrEqual _integerGreaterThanOrEqual _integerEquals _integerMultiply _integerAdd bnAdd bnSubtract bnMultiply bnDivide bnEquals bnModInverse Barrett makeIntegerUnop args makeIntegerBinOp args --- src/js/base/js-numbers.js | 354 ++++++++++++++++--------------- tests/jsnums-test/jsnums-test.js | 55 +++-- 2 files changed, 211 insertions(+), 198 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index ac0b14f14..0823aa954 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -234,7 +234,7 @@ define("pyret-base/js/js-numbers", function() { }; // isInteger: pyretnum -> boolean - var isInteger = function(n) { + var isInteger = function(n, errbacks) { if (typeof(n) === 'number') return Number.isInteger(n); if (isPyretNumber(n)) return n.isInteger(); return false; @@ -281,10 +281,10 @@ define("pyret-base/js/js-numbers", function() { }; // toFixnum: pyretnum -> javascript-number - var toFixnum = function(n) { + var toFixnum = function(n, errbacks) { if (typeof(n) === 'number') return n; - return n.toFixnum(); + return n.toFixnum(errbacks); }; // toRational: pyretnum -> pyretnum @@ -336,10 +336,10 @@ define("pyret-base/js/js-numbers", function() { return x.add(y); }, {isXSpecialCase: function(x, errbacks) { - return isInteger(x) && _integerIsZero(x) }, + return isInteger(x) && _integerIsZero(x, errbacks) }, onXSpecialCase: function(x, y, errbacks) { return y; }, isYSpecialCase: function(y, errbacks) { - return isInteger(y) && _integerIsZero(y) }, + return isInteger(y) && _integerIsZero(y, errbacks) }, onYSpecialCase: function(x, y, errbacks) { return x; } }); @@ -369,10 +369,10 @@ define("pyret-base/js/js-numbers", function() { return x.subtract(y); }, {isXSpecialCase: function(x, errbacks) { - return isInteger(x) && _integerIsZero(x) }, + return isInteger(x) && _integerIsZero(x, errbacks) }, onXSpecialCase: function(x, y, errbacks) { return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { - return isInteger(y) && _integerIsZero(y) }, + return isInteger(y) && _integerIsZero(y, errbacks) }, onYSpecialCase: function(x, y, errbacks) { return x; } }); @@ -403,24 +403,24 @@ define("pyret-base/js/js-numbers", function() { }, {isXSpecialCase: function(x, errbacks) { return (isInteger(x) && - (_integerIsZero(x) || _integerIsOne(x) || _integerIsNegativeOne(x))) }, + (_integerIsZero(x, errbacks) || _integerIsOne(x, errbacks) || _integerIsNegativeOne(x, errbacks))) }, onXSpecialCase: function(x, y, errbacks) { - if (_integerIsZero(x)) + if (_integerIsZero(x, errbacks)) return 0; - if (_integerIsOne(x)) + if (_integerIsOne(x, errbacks)) return y; - if (_integerIsNegativeOne(x)) + if (_integerIsNegativeOne(x, errbacks)) return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { - return (isInteger(y) && - (_integerIsZero(y) || _integerIsOne(y) || _integerIsNegativeOne(y)))}, + return (isInteger(y, errbacks) && + (_integerIsZero(y, errbacks) || _integerIsOne(y, errbacks) || _integerIsNegativeOne(y, errbacks)))}, onYSpecialCase: function(x, y, errbacks) { - if (_integerIsZero(y)) + if (_integerIsZero(y, errbacks)) return 0; - if (_integerIsOne(y)) + if (_integerIsOne(y, errbacks)) return x; - if (_integerIsNegativeOne(y)) + if (_integerIsNegativeOne(y, errbacks)) return negate(x, errbacks); } }); @@ -428,7 +428,7 @@ define("pyret-base/js/js-numbers", function() { // divide: pyretnum pyretnum -> pyretnum var divide = makeNumericBinop( function(x, y, errbacks) { - if (_integerIsZero(y)) + if (_integerIsZero(y, errbacks)) errbacks.throwDivByZero("/: division by zero, " + x + ' ' + y); var div = x / y; if (isOverflow(div)) { @@ -513,7 +513,7 @@ define("pyret-base/js/js-numbers", function() { if (isRoughnum(delta) && delta.n === Number.MIN_VALUE) { if ((isRoughnum(x) || isRoughnum(y)) && - (Math.abs(subtract(x,y).n) === Number.MIN_VALUE)) { + (Math.abs(subtract(x,y, errbacks).n) === Number.MIN_VALUE)) { errbacks.throwToleranceError("roughnum tolerance too small for meaningful comparison, " + x + ' ' + y + ' ' + delta); } } @@ -671,15 +671,15 @@ define("pyret-base/js/js-numbers", function() { // modulo: pyretnum pyretnum -> pyretnum var modulo = function(m, n, errbacks) { - if (! isInteger(m)) { + if (! isInteger(m, errbacks)) { errbacks.throwDomainError('modulo: the first argument ' + m + " is not an integer.", m, n); } - if (! isInteger(n)) { + if (! isInteger(n, errbacks)) { errbacks.throwDomainError('modulo: the second argument ' + n + " is not an integer.", m, n); } - if (_integerIsZero(n)) { + if (_integerIsZero(n, errbacks)) { errbacks.throwDomainError('modulo: the second argument is zero'); } var result; @@ -697,7 +697,7 @@ define("pyret-base/js/js-numbers", function() { return result; } } - result = _integerModulo(floor(m), floor(n)); + result = _integerModulo(floor(m), floor(n), errbacks); // The sign of the result should match the sign of n. if (lessThan(n, 0, errbacks)) { if (lessThanOrEqual(result, 0, errbacks)) { @@ -803,7 +803,7 @@ define("pyret-base/js/js-numbers", function() { if (typeof(n) === 'number') { return Roughnum.makeInstance(Math.log(n), errbacks); } - var nFix = n.toFixnum(); + var nFix = n.toFixnum(errbacks); if (typeof(nFix) === 'number' && nFix !== Infinity) { return Roughnum.makeInstance(Math.log(nFix), errbacks); } @@ -928,7 +928,7 @@ define("pyret-base/js/js-numbers", function() { // integerSqrt: pyretnum -> pyretnum var integerSqrt = function(x, errbacks) { - if (! isInteger(x)) { + if (! isInteger(x, errbacks)) { errbacks.throwDomainError('integer-sqrt: the argument ' + x.toString() + " is not an integer.", x); } @@ -944,38 +944,38 @@ define("pyret-base/js/js-numbers", function() { // gcd: pyretnum pyretnum -> pyretnum var gcd = function(first, second, errbacks) { - if (! isInteger(first)) { + if (! isInteger(first, errbacks)) { errbacks.throwDomainError('gcd: the argument ' + first.toString() + " is not an integer.", first); } - if (! isInteger(second)) { + if (! isInteger(second, errbacks)) { errbacks.throwDomainError('gcd: the argument ' + second.toString() + " is not an integer.", second); } var a = abs(first, errbacks), t; var b = abs(second, errbacks); - while (! _integerIsZero(b)) { + while (! _integerIsZero(b, errbacks)) { t = a; a = b; - b = _integerModulo(t, b); + b = _integerModulo(t, b, errbacks); } return a; }; // lcm: pyretnum pyretnum -> pyretnum var lcm = function(first, second, errbacks) { - if (! isInteger(first)) { + if (! isInteger(first, errbacks)) { errbacks.throwDomainError('lcm: the argument ' + first.toString() + " is not an integer.", first); } - if (! isInteger(second)) { + if (! isInteger(second, errbacks)) { errbacks.throwDomainError('lcm: the argument ' + second.toString() + " is not an integer.", second); } var result = abs(first, errbacks); - if (_integerIsZero(result)) { return 0; } - var divisor = _integerGcd(result, second); - if (_integerIsZero(divisor)) { + if (_integerIsZero(result, errbacks)) { return 0; } + var divisor = _integerGcd(result, second, errbacks); + if (_integerIsZero(divisor, errbacks)) { return 0; } result = divide(multiply(result, second, errbacks), divisor, errbacks); @@ -983,20 +983,20 @@ define("pyret-base/js/js-numbers", function() { }; var quotient = function(x, y, errbacks) { - if (! isInteger(x)) { + if (! isInteger(x, errbacks)) { errbacks.throwDomainError('quotient: the first argument ' + x.toString() + " is not an integer.", x); } - if (! isInteger(y)) { + if (! isInteger(y, errbacks)) { errbacks.throwDomainError('quotient: the second argument ' + y.toString() + " is not an integer.", y); } - return _integerQuotient(x, y); + return _integerQuotient(x, y, errbacks); }; var remainder = function(x, y, errbacks) { - if (isInteger(x) && isInteger(y)) { - return _integerRemainder(x, y); + if (isInteger(x, errbacks) && isInteger(y, errbacks)) { + return _integerRemainder(x, y, errbacks); } else if (isRational(x) && isRational(y)) { var xn = numerator(x, errbacks); var xd = denominator(x, errbacks); var yn = numerator(y, errbacks); var yd = denominator(y, errbacks); @@ -1043,7 +1043,7 @@ define("pyret-base/js/js-numbers", function() { var fastExpt = function(n, k, errbacks) { var acc = 1; while (true) { - if (_integerIsZero(k)) { + if (_integerIsZero(k, errbacks)) { return acc; } if (equals(modulo(k, 2, errbacks), 0, errbacks)) { @@ -1115,7 +1115,7 @@ define("pyret-base/js/js-numbers", function() { } } if (m instanceof Roughnum) { - return Roughnum.makeInstance(onFixnums(toFixnum(m), errbacks), errbacks); + return Roughnum.makeInstance(onFixnums(toFixnum(m, errbacks), errbacks), errbacks); } if (typeof(m) === 'number') { m = makeBignum(m); @@ -1126,16 +1126,16 @@ define("pyret-base/js/js-numbers", function() { // _integerModulo: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerModulo = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m % n; }, - function(m, n) { + function(m, n, errbacks) { return bnMod.call(m, n); }); // _integerGcd: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerGcd = makeIntegerBinop( - function(a, b) { + function(a, b, errbacks) { var t; while (b !== 0) { t = a; @@ -1144,80 +1144,80 @@ define("pyret-base/js/js-numbers", function() { } return a; }, - function(m, n) { + function(m, n, errbacks) { return bnGCD.call(m, n); }); // _integerIsZero: integer-pyretnum -> boolean // Returns true if the number is zero. var _integerIsZero = makeIntegerUnOp( - function(n){ + function(n, errbacks){ return n === 0; }, - function(n) { - return bnEquals.call(n, BigInteger.ZERO); + function(n, errbacks) { + return bnEquals.call(n, BigInteger.ZERO, errbacks); } ); // _integerIsOne: integer-pyretnum -> boolean var _integerIsOne = makeIntegerUnOp( - function(n) { + function(n, errbacks) { return n === 1; }, - function(n) { - return bnEquals.call(n, BigInteger.ONE); + function(n, errbacks) { + return bnEquals.call(n, BigInteger.ONE, errbacks); }); // _integerIsNegativeOne: integer-pyretnum -> boolean var _integerIsNegativeOne = makeIntegerUnOp( - function(n) { + function(n, errbacks) { return n === -1; }, - function(n) { - return bnEquals.call(n, BigInteger.NEGATIVE_ONE); + function(n, errbacks) { + return bnEquals.call(n, BigInteger.NEGATIVE_ONE, errbacks); }); // _integerAdd: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerAdd = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m + n; }, - function(m, n) { - return bnAdd.call(m, n); + function(m, n, errbacks) { + return bnAdd.call(m, n, errbacks); }); // _integerSubtract: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerSubtract = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m - n; }, - function(m, n) { - return bnSubtract.call(m, n); + function(m, n, errbacks) { + return bnSubtract.call(m, n, errbacks); }); // _integerMultiply: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerMultiply = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m * n; }, - function(m, n) { - return bnMultiply.call(m, n); + function(m, n, errbacks) { + return bnMultiply.call(m, n, errbacks); }); //_integerQuotient: integer-pyretnum integer-pyretnum -> integer-pyretnum var _integerQuotient = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return ((m - (m % n))/ n); }, - function(m, n) { - return bnDivide.call(m, n); + function(m, n, errbacks) { + return bnDivide.call(m, n, errbacks); }); var _integerRemainder = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m % n; }, - function(m, n) { + function(m, n, errbacks) { return bnRemainder.call(m, n); }); @@ -1270,10 +1270,10 @@ define("pyret-base/js/js-numbers", function() { // b = the exponents' difference // var _integerDivideToFixnum = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m / n; }, - function(m, n) { + function(m, n, errbacks) { var xm = splitIntIntoMantissaExpt(m); var xn = splitIntIntoMantissaExpt(n); var r = Number(String(xm[0] / xn[0]) + 'e' + @@ -1287,50 +1287,50 @@ define("pyret-base/js/js-numbers", function() { // _integerEquals: integer-pyretnum integer-pyretnum -> boolean var _integerEquals = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m === n; }, - function(m, n) { - return bnEquals.call(m, n); + function(m, n, errbacks) { + return bnEquals.call(m, n, errbacks); }, {doNotCoerceToFloating: true}); // _integerGreaterThan: integer-pyretnum integer-pyretnum -> boolean var _integerGreaterThan = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m > n; }, - function(m, n) { + function(m, n, errbacks) { return bnCompareTo.call(m, n) > 0; }, {doNotCoerceToFloating: true}); // _integerLessThan: integer-pyretnum integer-pyretnum -> boolean var _integerLessThan = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m < n; }, - function(m, n) { + function(m, n, errbacks) { return bnCompareTo.call(m, n) < 0; }, {doNotCoerceToFloating: true}); // _integerGreaterThanOrEqual: integer-pyretnum integer-pyretnum -> boolean var _integerGreaterThanOrEqual = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m >= n; }, - function(m, n) { + function(m, n, errbacks) { return bnCompareTo.call(m, n) >= 0; }, {doNotCoerceToFloating: true}); // _integerLessThanOrEqual: integer-pyretnum integer-pyretnum -> boolean var _integerLessThanOrEqual = makeIntegerBinop( - function(m, n) { + function(m, n, errbacks) { return m <= n; }, - function(m, n) { + function(m, n, errbacks) { return bnCompareTo.call(m, n) <= 0; }, {doNotCoerceToFloating: true}); @@ -1455,18 +1455,18 @@ define("pyret-base/js/js-numbers", function() { if (d === undefined) { d = 1; } - if (_integerLessThan(d, 0)) { + if (_integerLessThan(d, 0, errbacks)) { n = negate(n, errbacks); d = negate(d, errbacks); } - var divisor = _integerGcd(abs(n, errbacks), abs(d, errbacks)); - n = _integerQuotient(n, divisor); - d = _integerQuotient(d, divisor); + var divisor = _integerGcd(abs(n, errbacks), abs(d, errbacks), errbacks); + n = _integerQuotient(n, divisor, errbacks); + d = _integerQuotient(d, divisor, errbacks); // Optimization: if we can get around construction the rational // in favor of just returning n, do it: - if (_integerIsOne(d) || _integerIsZero(n)) { + if (_integerIsOne(d, errbacks) || _integerIsZero(n, errbacks)) { return n; } @@ -1474,7 +1474,9 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.toString = function() { - if (_integerIsOne(this.d)) { + // JS toString() doesn't take an errbacks arg, so + // we supply a dummy errbacks to _integerIsOne here + if (_integerIsOne(this.d, {})) { return this.n.toString() + ""; } else { return this.n.toString() + "/" + this.d.toString(); @@ -1487,12 +1489,12 @@ define("pyret-base/js/js-numbers", function() { Rational.prototype.equals = function(other, errbacks) { return (other instanceof Rational && - _integerEquals(this.n, other.n) && - _integerEquals(this.d, other.d)); + _integerEquals(this.n, other.n, errbacks) && + _integerEquals(this.d, other.d, errbacks)); }; - Rational.prototype.isInteger = function() { - return _integerIsOne(this.d); + Rational.prototype.isInteger = function(errbacks) { + return _integerIsOne(this.d, errbacks); }; Rational.prototype.isRational = function() { @@ -1527,15 +1529,19 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.add = function(other, errbacks) { - return Rational.makeInstance(_integerAdd(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)), - _integerMultiply(this.d, other.d), errbacks); + return Rational.makeInstance(_integerAdd(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks), + _integerMultiply(this.d, other.d, errbacks), + errbacks); }; Rational.prototype.subtract = function(other, errbacks) { - return Rational.makeInstance(_integerSubtract(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)), - _integerMultiply(this.d, other.d), errbacks); + return Rational.makeInstance(_integerSubtract(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks), + _integerMultiply(this.d, other.d, errbacks), + errbacks); }; Rational.prototype.negate = function(errbacks) { @@ -1543,16 +1549,18 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.multiply = function(other, errbacks) { - return Rational.makeInstance(_integerMultiply(this.n, other.n), - _integerMultiply(this.d, other.d), errbacks); + return Rational.makeInstance(_integerMultiply(this.n, other.n, errbacks), + _integerMultiply(this.d, other.d, errbacks), + errbacks); }; Rational.prototype.divide = function(other, errbacks) { - if (_integerIsZero(this.d) || _integerIsZero(other.n)) { // dead code! + if (_integerIsZero(this.d, errbacks) || _integerIsZero(other.n, errbacks)) { // dead code! errbacks.throwDivByZero("/: division by zero", this, other); } - return Rational.makeInstance(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n), errbacks); + return Rational.makeInstance(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks); }; Rational.prototype.toRational = function(errbacks) { @@ -1562,12 +1570,12 @@ define("pyret-base/js/js-numbers", function() { Rational.prototype.toExact = Rational.prototype.toRational; - Rational.prototype.toFixnum = function() { - return _integerDivideToFixnum(this.n, this.d); + Rational.prototype.toFixnum = function(errbacks) { + return _integerDivideToFixnum(this.n, this.d, errbacks); }; Rational.prototype.toRoughnum = function(errbacks) { - return Roughnum.makeInstance(this.toFixnum(), errbacks); + return Roughnum.makeInstance(this.toFixnum(errbacks), errbacks); }; Rational.prototype.numerator = function(errbacks) { @@ -1579,23 +1587,27 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.greaterThan = function(other, errbacks) { - return _integerGreaterThan(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)); + return _integerGreaterThan(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks); }; Rational.prototype.greaterThanOrEqual = function(other, errbacks) { - return _integerGreaterThanOrEqual(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)); + return _integerGreaterThanOrEqual(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks); }; Rational.prototype.lessThan = function(other, errbacks) { - return _integerLessThan(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)); + return _integerLessThan(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks); }; Rational.prototype.lessThanOrEqual = function(other, errbacks) { - return _integerLessThanOrEqual(_integerMultiply(this.n, other.d), - _integerMultiply(this.d, other.n)); + return _integerLessThanOrEqual(_integerMultiply(this.n, other.d, errbacks), + _integerMultiply(this.d, other.n, errbacks), + errbacks); }; Rational.prototype.integerSqrt = function(errbacks) { @@ -1621,8 +1633,8 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.floor = function(errbacks) { - var quotient = _integerQuotient(this.n, this.d); - if (_integerLessThan(this.n, 0)) { + var quotient = _integerQuotient(this.n, this.d, errbacks); + if (_integerLessThan(this.n, 0, errbacks)) { return subtract(quotient, 1, errbacks); } else { return quotient; @@ -1630,8 +1642,8 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.ceiling = function(errbacks) { - var quotient = _integerQuotient(this.n, this.d); - if (_integerLessThan(this.n, 0)) { + var quotient = _integerQuotient(this.n, this.d, errbacks); + if (_integerLessThan(this.n, 0, errbacks)) { return quotient; } else { return add(quotient, 1, errbacks); @@ -1640,19 +1652,19 @@ define("pyret-base/js/js-numbers", function() { Rational.prototype.round = function(errbacks) { var halfintp = equals(this.d, 2, errbacks); - var negativep = _integerLessThan(this.n, 0); + var negativep = _integerLessThan(this.n, 0, errbacks); var n = this.n; if (negativep) { n = negate(n, errbacks); } - var quo = _integerQuotient(n, this.d); + var quo = _integerQuotient(n, this.d, errbacks); if (halfintp) { // rounding half to away from 0 // uncomment following if rounding half to even - // if (_integerIsOne(_integerModulo(quo, 2))) + // if (_integerIsOne(_integerModulo(quo, 2, errbacks), errbacks)) quo = add(quo, 1, errbacks); } else { - var rem = _integerRemainder(n, this.d); + var rem = _integerRemainder(n, this.d, errbacks); if (greaterThan(multiply(rem, 2, errbacks), this.d, errbacks)) { quo = add(quo, 1, errbacks); } @@ -1666,15 +1678,15 @@ define("pyret-base/js/js-numbers", function() { Rational.prototype.roundEven = function(errbacks) { // rounds half-integers to even var halfintp = equals(this.d, 2, errbacks); - var negativep = _integerLessThan(this.n, 0); + var negativep = _integerLessThan(this.n, 0, errbacks); var n = this.n; if (negativep) n = negate(n, errbacks); - var quo = _integerQuotient(n, this.d); + var quo = _integerQuotient(n, this.d, errbacks); if (halfintp) { - if (_integerIsOne(_integerModulo(quo, 2))) + if (_integerIsOne(_integerModulo(quo, 2, errbacks), errbacks)) quo = add(quo, 1, errbacks); } else { - var rem = _integerRemainder(n, this.d); + var rem = _integerRemainder(n, this.d, errbacks); if (greaterThan(multiply(rem, 2, errbacks), this.d, errbacks)) quo = add(quo, 1, errbacks); } @@ -1683,23 +1695,23 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.log = function(errbacks){ - return Roughnum.makeInstance(Math.log(this.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.log(this.toFixnum(errbacks)), errbacks); }; Rational.prototype.tan = function(errbacks){ - return Roughnum.makeInstance(Math.tan(this.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.tan(this.toFixnum(errbacks)), errbacks); }; Rational.prototype.atan = function(errbacks){ - return Roughnum.makeInstance(Math.atan(this.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.atan(this.toFixnum(errbacks)), errbacks); }; Rational.prototype.cos = function(errbacks){ - return Roughnum.makeInstance(Math.cos(this.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.cos(this.toFixnum(errbacks)), errbacks); }; Rational.prototype.sin = function(errbacks){ - return Roughnum.makeInstance(Math.sin(this.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.sin(this.toFixnum(errbacks)), errbacks); }; var integerNthRoot = function(n, m, errbacks) { @@ -1736,7 +1748,7 @@ define("pyret-base/js/js-numbers", function() { var mAbs = (mNeg ? abs(m, errbacks) : m); var approx; - if (mNeg && _integerModulo(n, 2) === 0) + if (mNeg && _integerModulo(n, 2, errbacks) === 0) errbacks.throwDomainError('expt: taking even (' + n + ') root of negative integer ' + m); approx = integerNthRoot(n, mAbs, errbacks); @@ -1749,9 +1761,9 @@ define("pyret-base/js/js-numbers", function() { }; Rational.prototype.expt = function(a, errbacks) { - if (isInteger(a) && greaterThanOrEqual(a, 0, errbacks)) { + if (isInteger(a, errbacks) && greaterThanOrEqual(a, 0, errbacks)) { return fastExpt(this, a, errbacks); - } else if (_integerLessThanOrEqual(a.d, 8)) { + } else if (_integerLessThanOrEqual(a.d, 8, errbacks)) { var nRaisedToAn = expt(this.n, a.n, errbacks); var dRaisedToAn = expt(this.d, a.n, errbacks); var newN = nthRoot(a.d, nRaisedToAn, errbacks); @@ -1764,25 +1776,26 @@ define("pyret-base/js/js-numbers", function() { return divide(newN, newD, errbacks); } } else { - if (this.isNegative() && !a.isInteger()) + if (this.isNegative() && !a.isInteger(errbacks)) errbacks.throwDomainError('expt: raising negative number ' + this + ' to nonintegral power ' + a); - return Roughnum.makeInstance(Math.pow(this.toFixnum(), a.toFixnum()), errbacks); + return Roughnum.makeInstance(Math.pow(this.toFixnum(errbacks), a.toFixnum(errbacks)), + errbacks); } }; Rational.prototype.exp = function(errbacks){ - var res = Math.exp(this.toFixnum()); + var res = Math.exp(this.toFixnum(errbacks)); if (!isFinite(res)) errbacks.throwDomainError('exp: argument too large: ' + this); return Roughnum.makeInstance(res, errbacks); }; Rational.prototype.acos = function(errbacks){ - return acos(this.toFixnum(), errbacks); + return acos(this.toFixnum(errbacks), errbacks); }; Rational.prototype.asin = function(errbacks){ - return asin(this.toFixnum(), errbacks); + return asin(this.toFixnum(errbacks), errbacks); }; // sign: Number -> {-1, 0, 1} @@ -1841,7 +1854,7 @@ define("pyret-base/js/js-numbers", function() { Roughnum.prototype.isExact = Roughnum.prototype.isRational; - Roughnum.prototype.isInteger = function() { + Roughnum.prototype.isInteger = function(errbacks) { return false; }; @@ -1889,7 +1902,7 @@ define("pyret-base/js/js-numbers", function() { return Roughnum.makeInstance(this.n / other.n, errbacks); }; - Roughnum.prototype.toFixnum = function() { + Roughnum.prototype.toFixnum = function(errbacks) { return this.n; }; @@ -1903,7 +1916,7 @@ define("pyret-base/js/js-numbers", function() { if (match) { var afterDecimal = parseInt(match[2]); var factorToInt = Math.pow(10, match[2].length); - var extraFactor = _integerGcd(factorToInt, afterDecimal); + var extraFactor = _integerGcd(factorToInt, afterDecimal, errbacks); var multFactor = factorToInt / extraFactor; return Roughnum.makeInstance( Math.round(this.n * multFactor) ); } else { @@ -1917,7 +1930,7 @@ define("pyret-base/js/js-numbers", function() { if (match) { var afterDecimal = parseInt(match[2]); var factorToInt = Math.pow(10, match[2].length); - var extraFactor = _integerGcd(factorToInt, afterDecimal); + var extraFactor = _integerGcd(factorToInt, afterDecimal, errbacks); return Roughnum.makeInstance( Math.round(factorToInt/extraFactor) ); } else { return Roughnum.makeInstance(1); @@ -1968,7 +1981,7 @@ define("pyret-base/js/js-numbers", function() { }; Roughnum.prototype.integerSqrt = function(errbacks) { - if (isInteger(this)) { + if (isInteger(this, errbacks)) { if(this.n >= 0) { return Roughnum.makeInstance(Math.floor(Math.sqrt(this.n)), errbacks); } else { @@ -2096,16 +2109,17 @@ define("pyret-base/js/js-numbers", function() { } var finalDen = denominatorTen; - var finalNum = _integerAdd(_integerMultiply(beforeDecimal, denominatorTen), afterDecimal); + var finalNum = _integerAdd(_integerMultiply(beforeDecimal, denominatorTen, errbacks), + afterDecimal, errbacks); if (negativeP) { finalNum = negate(finalNum, errbacks); } // if (!equals(exponent, 1, errbacks)) { if (exponentNegativeP) { - finalDen = _integerMultiply(finalDen, exponent); + finalDen = _integerMultiply(finalDen, exponent, errbacks); } else { - finalNum = _integerMultiply(finalNum, exponent); + finalNum = _integerMultiply(finalNum, exponent, errbacks); } } return Rational.makeInstance(finalNum, finalDen, errbacks); @@ -3097,7 +3111,7 @@ define("pyret-base/js/js-numbers", function() { return r; } - function bnEquals(a) { return(this.compareTo(a)==0); } + function bnEquals(a, errbacks) { return(this.compareTo(a)==0); } function bnMin(a) { return(this.compareTo(a)<0)?this:a; } function bnMax(a) { return(this.compareTo(a)>0)?this:a; } @@ -3249,16 +3263,16 @@ define("pyret-base/js/js-numbers", function() { } // (public) this + a - function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } + function bnAdd(a, errbacks) { var r = nbi(); this.addTo(a,r); return r; } // (public) this - a - function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } + function bnSubtract(a, errbacks) { var r = nbi(); this.subTo(a,r); return r; } // (public) this * a - function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } + function bnMultiply(a, errbacks) { var r = nbi(); this.multiplyTo(a,r); return r; } // (public) this / a - function bnDivide(a) { + function bnDivide(a, errbacks) { var r = nbi(); this.divRemTo(a,r,null); r.clamp(); return r; } @@ -3335,12 +3349,12 @@ define("pyret-base/js/js-numbers", function() { } // Barrett modular reduction - function Barrett(m) { + function Barrett(m, errbacks) { // setup Barrett this.r2 = nbi(); this.q3 = nbi(); BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); + this.mu = this.r2.divide(m, errbacks); this.m = m; } @@ -3387,7 +3401,7 @@ define("pyret-base/js/js-numbers", function() { if(i < 8) z = new Classic(m); else if(m.isEven()) - z = new Barrett(m); + z = new Barrett(m, errbacks); else z = new Montgomery(m); @@ -3473,7 +3487,7 @@ define("pyret-base/js/js-numbers", function() { } // (public) 1/this % m (HAC 14.61) - function bnModInverse(m) { + function bnModInverse(m, errbacks) { var ac = m.isEven(); if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; var u = m.clone(), v = this.clone(); @@ -3511,7 +3525,7 @@ define("pyret-base/js/js-numbers", function() { if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; if(d.compareTo(m) >= 0) return d.subtract(m); if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; + if(d.signum() < 0) return d.add(m, errbacks); else return d; } var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509]; @@ -3656,7 +3670,7 @@ define("pyret-base/js/js-numbers", function() { return true; }; - BigInteger.prototype.isInteger = function() { + BigInteger.prototype.isInteger = function(errbacks) { return true; }; @@ -3696,7 +3710,7 @@ define("pyret-base/js/js-numbers", function() { BigInteger.prototype.toExact = BigInteger.prototype.toRational; - BigInteger.prototype.toFixnum = function() { + BigInteger.prototype.toFixnum = function(errbacks) { var a = splitIntIntoMantissaExpt(this); //console.log('bigint.tofixnum of', this); //console.log('split = ', a); @@ -3706,7 +3720,7 @@ define("pyret-base/js/js-numbers", function() { } BigInteger.prototype.toRoughnum = function(errbacks) { - return Roughnum.makeInstance(this.toFixnum(), errbacks); + return Roughnum.makeInstance(this.toFixnum(errbacks), errbacks); }; BigInteger.prototype.greaterThan = function(other, errbacks) { @@ -3817,31 +3831,31 @@ define("pyret-base/js/js-numbers", function() { // log: -> pyretnum // Produce the log. BigInteger.prototype.log = function(errbacks) { - return log(this.toFixnum(), errbacks); + return log(this.toFixnum(errbacks), errbacks); }; // tan: -> pyretnum // Produce the tan. BigInteger.prototype.tan = function(errbacks) { - return tan(this.toFixnum(), errbacks); + return tan(this.toFixnum(errbacks), errbacks); }; // atan: -> pyretnum // Produce the arc tangent. BigInteger.prototype.atan = function(errbacks) { - return atan(this.toFixnum(), errbacks); + return atan(this.toFixnum(errbacks), errbacks); }; // cos: -> pyretnum // Produce the cosine. BigInteger.prototype.cos = function(errbacks) { - return cos(this.toFixnum(), errbacks); + return cos(this.toFixnum(errbacks), errbacks); }; // sin: -> pyretnum // Produce the sine. BigInteger.prototype.sin = function(errbacks) { - return sin(this.toFixnum(), errbacks); + return sin(this.toFixnum(errbacks), errbacks); }; // expt: pyretnum -> pyretnum @@ -3853,7 +3867,7 @@ define("pyret-base/js/js-numbers", function() { // exp: -> pyretnum // Produce e raised to the given power. BigInteger.prototype.exp = function(errbacks) { - var res = Math.exp(this.toFixnum()); + var res = Math.exp(this.toFixnum(errbacks)); if (!isFinite(res)) errbacks.throwDomainError('exp: argument too large: ' + this); return Roughnum.makeInstance(res, errbacks); @@ -3862,13 +3876,13 @@ define("pyret-base/js/js-numbers", function() { // acos: -> pyretnum // Produce the arc cosine. BigInteger.prototype.acos = function(errbacks) { - return acos(this.toFixnum(), errbacks); + return acos(this.toFixnum(errbacks), errbacks); }; // asin: -> pyretnum // Produce the arc sine. BigInteger.prototype.asin = function(errbacks) { - return asin(this.toFixnum(), errbacks); + return asin(this.toFixnum(errbacks), errbacks); }; ////////////////////////////////////////////////////////////////////// @@ -3955,11 +3969,11 @@ define("pyret-base/js/js-numbers", function() { if (options && typeof(options.limit) !== 'undefined') { limit = options.limit; } - if (! isInteger(n)) { + if (! isInteger(n, errbacks)) { errbacks.throwDomainError('toRepeatingDecimal: n ' + n.toString() + " is not an integer."); } - if (! isInteger(d)) { + if (! isInteger(d, errbacks)) { errbacks.throwDomainError('toRepeatingDecimal: d ' + d.toString() + " is not an integer."); } @@ -3988,13 +4002,13 @@ define("pyret-base/js/js-numbers", function() { // input number, which may have been an approximation, or unrepresentable in // decimal. function toStringDigits(n, digits, errbacks) { - if (!isInteger(digits)) { + if (!isInteger(digits, errbacks)) { errbacks.throwDomainError('num-to-string-digits: digits should be an integer'); } var tenDigits = expt(10, digits, errbacks); var d = toFixnum(digits); n = divide(round(multiply(n, tenDigits, errbacks), errbacks), tenDigits, errbacks); - if (isInteger(n)) { + if (isInteger(n, errbacks)) { var ans = n.toString(); if (d >= 1) { ans += '.'; diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 2ae19a201..a110233f3 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -288,14 +288,13 @@ R(["pyret-base/js/js-numbers"], function(JN) { it('number casts', function() { - // toFixnum (why no errbacks?) - expect(JN.toFixnum(5)).toBe(5); - expect(JN.toFixnum(-5)).toBe(-5); - expect(JN.toFixnum(0)).toBe(0); - expect(JN.toFixnum(3.14)).toBe(3.14); - expect(JN.toFixnum(JN.Rational.makeInstance(355, 113, sampleErrbacks))).toBe(355/113); - expect(JN.toFixnum(JN.expt(10, 400, sampleErrbacks))).toBe(Infinity); - expect(JN.toFixnum(JN.Roughnum.makeInstance(2.718, sampleErrbacks))).toBe(2.718); + expect(JN.toFixnum(5, sampleErrbacks)).toBe(5); + expect(JN.toFixnum(-5, sampleErrbacks)).toBe(-5); + expect(JN.toFixnum(0, sampleErrbacks)).toBe(0); + expect(JN.toFixnum(3.14, sampleErrbacks)).toBe(3.14); + expect(JN.toFixnum(JN.Rational.makeInstance(355, 113, sampleErrbacks), sampleErrbacks)).toBe(355/113); + expect(JN.toFixnum(JN.expt(10, 400, sampleErrbacks), sampleErrbacks)).toBe(Infinity); + expect(JN.toFixnum(JN.Roughnum.makeInstance(2.718, sampleErrbacks), sampleErrbacks)).toBe(2.718); // toRational (toExact is its alias) expect(JN.toRational(5, sampleErrbacks)).toBe(5); @@ -349,7 +348,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(function() { JN.roughlyEquals( JN.toRoughnum(JN.expt(10, 400), sampleErrbacks), - JN.toFixnum(Infinity), + JN.toFixnum(Infinity, sampleErrbacks), 0.00001, sampleErrbacks); }) .toThrowError(/overflow/); @@ -627,43 +626,43 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN._innards.splitIntIntoMantissaExpt('111222333444555666777888999')) .toEqual([1.1122233344455567, 26]); - expect(JN._innards._integerDivideToFixnum(2, 3)) + expect(JN._innards._integerDivideToFixnum(2, 3, sampleErrbacks)) .toEqual(2/3); expect(JN._innards._integerDivideToFixnum(JN.makeBignum('2e311'), - JN.makeBignum('3e311'))) + JN.makeBignum('3e311'), sampleErrbacks)) .toEqual(2/3); - expect(JN._innards._integerEquals(2,2)) + expect(JN._innards._integerEquals(2,2, sampleErrbacks)) .toEqual(true); expect(JN._innards._integerEquals(JN.makeBignum('2e311'), - JN.makeBignum('2e311'))) + JN.makeBignum('2e311'), sampleErrbacks)) .toEqual(true); - expect(JN._innards._integerEquals(2,3)) + expect(JN._innards._integerEquals(2,3, sampleErrbacks)) .toEqual(false); expect(JN._innards._integerEquals(JN.makeBignum('2e311'), - JN.makeBignum('3e311'))) + JN.makeBignum('3e311'), sampleErrbacks)) .toEqual(false); - expect(JN._innards._integerGreaterThan(2,2)) + expect(JN._innards._integerGreaterThan(2,2, sampleErrbacks)) .toEqual(false); expect(JN._innards._integerGreaterThan(JN.makeBignum('2e311'), - JN.makeBignum('2e311'))) + JN.makeBignum('2e311'), sampleErrbacks)) .toEqual(false); - expect(JN._innards._integerGreaterThan(2,3)) + expect(JN._innards._integerGreaterThan(2,3, sampleErrbacks)) .toEqual(false); expect(JN._innards._integerGreaterThan(JN.makeBignum('2e311'), - JN.makeBignum('3e311'))) + JN.makeBignum('3e311'), sampleErrbacks)) .toEqual(false); - expect(JN._innards._integerLessThan(2,2)) + expect(JN._innards._integerLessThan(2,2, sampleErrbacks)) .toEqual(false); expect(JN._innards._integerLessThan(JN.makeBignum('2e311'), - JN.makeBignum('2e311'))) + JN.makeBignum('2e311'), sampleErrbacks)) .toEqual(false); - expect(JN._innards._integerLessThan(2,3)) + expect(JN._innards._integerLessThan(2,3, sampleErrbacks)) .toEqual(true); expect(JN._innards._integerLessThan(JN.makeBignum('2e311'), - JN.makeBignum('3e311'))) + JN.makeBignum('3e311'), sampleErrbacks)) .toEqual(true); expect(JN._innards._integerGreaterThanOrEqual(2,2)) @@ -857,7 +856,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { // toRational? expect(JN.Rational.makeInstance(2, 3, sampleErrbacks).isRational()).toBe(true); - expect(JN.Rational.makeInstance(2, 4, sampleErrbacks).toFixnum()).toEqual(0.5); + expect(JN.Rational.makeInstance(2, 4, sampleErrbacks).toFixnum(sampleErrbacks)).toEqual(0.5); expect(JN.Rational.makeInstance(4, 6, sampleErrbacks) .numerator(sampleErrbacks)) @@ -987,7 +986,7 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(function () { JN.Roughnum.makeInstance(undefined, sampleErrbacks); }) .toThrowError(/unsuitable/); - expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(), 3.14)).toBe(true); + expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(sampleErrbacks), 3.14)).toBe(true); expect(JN.roughlyEquals(JN.Roughnum.makeInstance(3.14, sampleErrbacks), 3.14, 0.0001, sampleErrbacks)) @@ -995,15 +994,15 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks).isRoughnum()).toBe(true); - expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(), 3.14)).toBe(true); + expect(JN.equals(JN.Roughnum.makeInstance(3.14, sampleErrbacks).toFixnum(sampleErrbacks), 3.14)).toBe(true); // shouldn't roughnum's numerator method take errbacks? expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks) - .numerator(sampleErrbacks).toFixnum()) + .numerator(sampleErrbacks).toFixnum(sampleErrbacks)) .toEqual(157); expect(JN.Roughnum.makeInstance(3.14, sampleErrbacks) - .denominator(sampleErrbacks).toFixnum()) + .denominator(sampleErrbacks).toFixnum(sampleErrbacks)) .toEqual(50); expect(JN.equals( From b9525826258d04e7ede2715fcf86bdae16fb6386 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 13 Oct 2025 08:46:59 -0400 Subject: [PATCH 31/36] Following defined but unused -- identify for now, potentially remove later: bnByteValue bnShortValue bnToByteArray bnMin bnMax bnAnd bnOr bnXor bnAndNot bnNot bnBitCount bnSetBit bnClearBit bnFlipBit bnModInverse --- src/js/base/js-numbers.js | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 0823aa954..a51551309 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -3003,10 +3003,10 @@ define("pyret-base/js/js-numbers", function() { } // (public) return value as byte - function bnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; } + function OBSbnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; } // (public) return value as short (assumes DB>=16) - function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } + function OBSbnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } // (protected) return x s.t. r^x < DV function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } @@ -3087,7 +3087,7 @@ define("pyret-base/js/js-numbers", function() { } // (public) convert to bigendian byte array - function bnToByteArray() { + function OBSbnToByteArray() { var i = this.t, r = []; r[0] = this.s; var p = this.DB-(i*this.DB)%8, d, k = 0; @@ -3112,8 +3112,8 @@ define("pyret-base/js/js-numbers", function() { } function bnEquals(a, errbacks) { return(this.compareTo(a)==0); } - function bnMin(a) { return(this.compareTo(a)<0)?this:a; } - function bnMax(a) { return(this.compareTo(a)>0)?this:a; } + function OBSbnMin(a) { return(this.compareTo(a)<0)?this:a; } + function OBSbnMax(a) { return(this.compareTo(a)>0)?this:a; } // (protected) r = this op a (bitwise) function bnpBitwiseTo(a,op,r) { @@ -3135,22 +3135,22 @@ define("pyret-base/js/js-numbers", function() { // (public) this & a function op_and(x,y) { return x&y; } - function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } + function OBSbnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } // (public) this | a function op_or(x,y) { return x|y; } - function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } + function OBSbnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } // (public) this ^ a function op_xor(x,y) { return x^y; } - function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } + function OBSbnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } // (public) this & ~a function op_andnot(x,y) { return x&~y; } - function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } + function OBSbnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } // (public) ~this - function bnNot() { + function OBSbnNot() { var r = nbi(); for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; r.t = this.t; @@ -3200,7 +3200,7 @@ define("pyret-base/js/js-numbers", function() { } // (public) return number of set bits - function bnBitCount() { + function OBSbnBitCount() { var r = 0, x = this.s&this.DM; for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); return r; @@ -3221,13 +3221,13 @@ define("pyret-base/js/js-numbers", function() { } // (public) this | (1< Date: Mon, 13 Oct 2025 09:02:08 -0400 Subject: [PATCH 32/36] =?UTF-8?q?Don't=20=CE=B1-rename=20potential=20unusu?= =?UTF-8?q?ed=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/base/js-numbers.js | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index a51551309..39818e1b3 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -3003,10 +3003,10 @@ define("pyret-base/js/js-numbers", function() { } // (public) return value as byte - function OBSbnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; } + function bnByteValue() { return (this.t==0)?this.s:(this[0]<<24)>>24; } // (public) return value as short (assumes DB>=16) - function OBSbnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } + function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } // (protected) return x s.t. r^x < DV function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } @@ -3087,7 +3087,7 @@ define("pyret-base/js/js-numbers", function() { } // (public) convert to bigendian byte array - function OBSbnToByteArray() { + function bnToByteArray() { var i = this.t, r = []; r[0] = this.s; var p = this.DB-(i*this.DB)%8, d, k = 0; @@ -3112,8 +3112,8 @@ define("pyret-base/js/js-numbers", function() { } function bnEquals(a, errbacks) { return(this.compareTo(a)==0); } - function OBSbnMin(a) { return(this.compareTo(a)<0)?this:a; } - function OBSbnMax(a) { return(this.compareTo(a)>0)?this:a; } + function bnMin(a) { return(this.compareTo(a)<0)?this:a; } + function bnMax(a) { return(this.compareTo(a)>0)?this:a; } // (protected) r = this op a (bitwise) function bnpBitwiseTo(a,op,r) { @@ -3135,22 +3135,22 @@ define("pyret-base/js/js-numbers", function() { // (public) this & a function op_and(x,y) { return x&y; } - function OBSbnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } + function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } // (public) this | a function op_or(x,y) { return x|y; } - function OBSbnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } + function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } // (public) this ^ a function op_xor(x,y) { return x^y; } - function OBSbnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } + function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } // (public) this & ~a function op_andnot(x,y) { return x&~y; } - function OBSbnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } + function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } // (public) ~this - function OBSbnNot() { + function bnNot() { var r = nbi(); for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; r.t = this.t; @@ -3200,7 +3200,7 @@ define("pyret-base/js/js-numbers", function() { } // (public) return number of set bits - function OBSbnBitCount() { + function bnBitCount() { var r = 0, x = this.s&this.DM; for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); return r; @@ -3221,13 +3221,13 @@ define("pyret-base/js/js-numbers", function() { } // (public) this | (1< Date: Mon, 13 Oct 2025 11:43:11 -0400 Subject: [PATCH 33/36] `make test` should run tests/jsnums-test/jsnums-test.js --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6af0a8428..329ef452f 100644 --- a/Makefile +++ b/Makefile @@ -238,7 +238,7 @@ TEST_BUILD=$(NODE) $(PYRET_TEST_PHASE)/pyret.jarr \ test-all: test .PHONY : test -test: pyret-test type-check-test pyret-io-test +test: pyret-test type-check-test pyret-io-test pyret-jsnums-test .PHONY : parse-test parse-test: tests/parse/parse.js build/phaseA/js/pyret-tokenizer.js build/phaseA/js/pyret-parser.js @@ -274,6 +274,11 @@ pyret-test: phaseA tests/pyret/main2.jarr pyret-io-test: phaseA $(NPM_EXEC) jest --detectOpenHandles --forceExit --verbose "tests/io-tests/io.test.js" +.PHONY : pyret-jsnums-test +pyret-jsnums-test: phaseA + echo doing pyret-jsnums-test + $(NODE) tests/jsnums-test/jsnums-test.js + .PHONY : regression-test regression-test: tests/pyret/regression.jarr $(NODE) tests/pyret/regression.jarr From 486dd9c01dc1f35a5f103e3b2e45f520676598fd Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 13 Oct 2025 11:51:56 -0400 Subject: [PATCH 34/36] arrayEquals() not needed in jsnums-test.js --- tests/jsnums-test/jsnums-test.js | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index a110233f3..4d4e47db8 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -20,18 +20,6 @@ R(["pyret-base/js/js-numbers"], function(JN) { throwGeneralError: function(x) { throw new Error('generalError ' + x); }, }; - function arrayEquals(A, B) { - if (A === B) return true; - if (!Array.isArray(A) || !Array.isArray(B)) return false; - // both are arrays - var n = A.length; - if (B.length !== n) return false; - for (var i = 0; i < n; i++) { - if (!arrayEquals(A[i], B[i])) return false; - } - return true; - } - describe("check functions that don't allow testing via Pyret programs", function() { it("make*opFun", function() { @@ -358,14 +346,10 @@ R(["pyret-base/js/js-numbers"], function(JN) { it('other subrs', function() { // toRepeatingDecimal - expect(arrayEquals(JN.toRepeatingDecimal(883, 700, undefined, sampleErrbacks), - ['1', '26', '142857'], - undefined, sampleErrbacks)) - .toBe(true); - expect(arrayEquals(JN.toRepeatingDecimal(883, 700, {limit: 2}, sampleErrbacks), - ['1', '26', '...'], - undefined, sampleErrbacks)) - .toBe(true); + expect(JN.toRepeatingDecimal(883, 700, undefined, sampleErrbacks)) + .toEqual(['1', '26', '142857']); + expect(JN.toRepeatingDecimal(883, 700, {limit: 2}, sampleErrbacks)) + .toEqual(['1', '26', '...']); expect(function() { JN.toRepeatingDecimal(355/113, 10, undefined, sampleErrbacks); }).toThrowError(/not an integer/); @@ -401,10 +385,10 @@ R(["pyret-base/js/js-numbers"], function(JN) { JN.toRepeatingDecimal(944473296573929, JN.makeBignum(0), undefined, sampleErrbacks); }).toThrowError(/d equals 0/); - expect(arrayEquals(JN._innards.getResidue(183, 700, 512, sampleErrbacks), ['26', '142857'])) - .toBe(true); - expect(arrayEquals(JN._innards.getResidue(183, 700, 2, sampleErrbacks), ['26', '...'])) - .toBe(true); + expect(JN._innards.getResidue(183, 700, 512, sampleErrbacks)) + .toEqual(['26', '142857']); + expect(JN._innards.getResidue(183, 700, 2, sampleErrbacks)) + .toEqual(['26', '...']); // toStringDigits expect(JN.toStringDigits(123456789, 5, sampleErrbacks)) From 3c0f18fd4c2bcf1c2c6292c032c0da78a02734c5 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 13 Oct 2025 12:02:24 -0400 Subject: [PATCH 35/36] function and method isInteger() call should take errbacks --- src/js/base/js-numbers.js | 14 +++++++------- tests/jsnums-test/jsnums-test.js | 16 +++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 39818e1b3..3b875da46 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -235,8 +235,8 @@ define("pyret-base/js/js-numbers", function() { // isInteger: pyretnum -> boolean var isInteger = function(n, errbacks) { - if (typeof(n) === 'number') return Number.isInteger(n); - if (isPyretNumber(n)) return n.isInteger(); + if (typeof(n) === 'number') return Number.isInteger(n, errbacks); + if (isPyretNumber(n)) return n.isInteger(errbacks); return false; }; @@ -336,10 +336,10 @@ define("pyret-base/js/js-numbers", function() { return x.add(y); }, {isXSpecialCase: function(x, errbacks) { - return isInteger(x) && _integerIsZero(x, errbacks) }, + return isInteger(x, errbacks) && _integerIsZero(x, errbacks) }, onXSpecialCase: function(x, y, errbacks) { return y; }, isYSpecialCase: function(y, errbacks) { - return isInteger(y) && _integerIsZero(y, errbacks) }, + return isInteger(y, errbacks) && _integerIsZero(y, errbacks) }, onYSpecialCase: function(x, y, errbacks) { return x; } }); @@ -369,10 +369,10 @@ define("pyret-base/js/js-numbers", function() { return x.subtract(y); }, {isXSpecialCase: function(x, errbacks) { - return isInteger(x) && _integerIsZero(x, errbacks) }, + return isInteger(x, errbacks) && _integerIsZero(x, errbacks) }, onXSpecialCase: function(x, y, errbacks) { return negate(y, errbacks); }, isYSpecialCase: function(y, errbacks) { - return isInteger(y) && _integerIsZero(y, errbacks) }, + return isInteger(y, errbacks) && _integerIsZero(y, errbacks) }, onYSpecialCase: function(x, y, errbacks) { return x; } }); @@ -402,7 +402,7 @@ define("pyret-base/js/js-numbers", function() { return x.multiply(y, errbacks); }, {isXSpecialCase: function(x, errbacks) { - return (isInteger(x) && + return (isInteger(x, errbacks) && (_integerIsZero(x, errbacks) || _integerIsOne(x, errbacks) || _integerIsNegativeOne(x, errbacks))) }, onXSpecialCase: function(x, y, errbacks) { if (_integerIsZero(x, errbacks)) diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index 4d4e47db8..cb3b886d1 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -219,13 +219,15 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.isReal(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(true); // isInteger - expect(JN.isInteger(5)).toBe(true); - expect(JN.isInteger(-5)).toBe(true); - expect(JN.isInteger(0)).toBe(true); - expect(JN.isInteger(3.14)).toBe(false); - expect(JN.isInteger(JN.Rational.makeInstance(355,113,sampleErrbacks))).toBe(false); - expect(JN.isInteger(JN.expt(10, 400))).toBe(true); - expect(JN.isInteger(JN.Roughnum.makeInstance(2.718,sampleErrbacks))).toBe(false); + expect(JN.isInteger(5, sampleErrbacks)).toBe(true); + expect(JN.isInteger(-5, sampleErrbacks)).toBe(true); + expect(JN.isInteger(0, sampleErrbacks)).toBe(true); + expect(JN.isInteger(3.14, sampleErrbacks)).toBe(false); + expect(JN.isInteger(JN.Rational.makeInstance(355,113,sampleErrbacks), + sampleErrbacks)).toBe(false); + expect(JN.isInteger(JN.expt(10, 400), sampleErrbacks)).toBe(true); + expect(JN.isInteger(JN.Roughnum.makeInstance(2.718,sampleErrbacks)), sampleErrbacks) + .toBe(false); // isRoughnum expect(JN.isRoughnum(5)).toBe(false); From 66d11d2f256fcfda7963243597c90ec4238a7beb Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 13 Oct 2025 12:19:04 -0400 Subject: [PATCH 36/36] - Ensure function and method toFixnum() calls take errbacks - jsnums-test is the makefile target name for testing js-numbers.js --- Makefile | 7 +++---- src/js/base/js-numbers.js | 13 +++++++------ tests/jsnums-test/jsnums-test.js | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 329ef452f..c074be41b 100644 --- a/Makefile +++ b/Makefile @@ -238,7 +238,7 @@ TEST_BUILD=$(NODE) $(PYRET_TEST_PHASE)/pyret.jarr \ test-all: test .PHONY : test -test: pyret-test type-check-test pyret-io-test pyret-jsnums-test +test: pyret-test type-check-test pyret-io-test jsnums-test .PHONY : parse-test parse-test: tests/parse/parse.js build/phaseA/js/pyret-tokenizer.js build/phaseA/js/pyret-parser.js @@ -274,9 +274,8 @@ pyret-test: phaseA tests/pyret/main2.jarr pyret-io-test: phaseA $(NPM_EXEC) jest --detectOpenHandles --forceExit --verbose "tests/io-tests/io.test.js" -.PHONY : pyret-jsnums-test -pyret-jsnums-test: phaseA - echo doing pyret-jsnums-test +.PHONY : jsnums-test +jsnums-test: phaseA $(NODE) tests/jsnums-test/jsnums-test.js .PHONY : regression-test diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 3b875da46..83a18eda8 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -1005,7 +1005,7 @@ define("pyret-base/js/js-numbers", function() { var new_yn = multiply(yn, divide(new_d, yd, errbacks), errbacks); return divide(remainder(new_xn, new_yn, errbacks), new_d, errbacks); } else { - var res = toFixnum(x) % toFixnum(y); + var res = toFixnum(x, errbacks) % toFixnum(y, errbacks); return Roughnum.makeInstance(res, errbacks); } }; @@ -1088,7 +1088,7 @@ define("pyret-base/js/js-numbers", function() { } if (m instanceof Roughnum || n instanceof Roughnum) { return Roughnum.makeInstance( - onFixnums(toFixnum(m), toFixnum(n), errbacks), errbacks); + onFixnums(toFixnum(m, errbacks), toFixnum(n, errbacks), errbacks), errbacks); } if (typeof(m) === 'number') { m = makeBignum(m); @@ -1755,8 +1755,9 @@ define("pyret-base/js/js-numbers", function() { if (mNeg) approx = negate(approx, errbacks); if (eqv(expt(approx, n, errbacks), m, errbacks)) return approx; - approx = Roughnum.makeInstance(Math.pow(toFixnum(mAbs), - toFixnum(divide(1,n, errbacks))), errbacks); + approx = Roughnum.makeInstance(Math.pow(toFixnum(mAbs, errbacks), + toFixnum(divide(1,n, errbacks), errbacks)), + errbacks); return (mNeg ? negate(approx, errbacks) : approx); }; @@ -3793,7 +3794,7 @@ define("pyret-base/js/js-numbers", function() { if (eqv(sqr(approx, errbacks), this, errbacks)) { return approx; } - fix = toFixnum(this); + fix = toFixnum(this, errbacks); if (isFinite(fix)) { return Roughnum.makeInstance(Math.sqrt(fix), errbacks); } else { @@ -4006,7 +4007,7 @@ define("pyret-base/js/js-numbers", function() { errbacks.throwDomainError('num-to-string-digits: digits should be an integer'); } var tenDigits = expt(10, digits, errbacks); - var d = toFixnum(digits); + var d = toFixnum(digits, errbacks); n = divide(round(multiply(n, tenDigits, errbacks), errbacks), tenDigits, errbacks); if (isInteger(n, errbacks)) { var ans = n.toString(); diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js index cb3b886d1..056a4fa99 100644 --- a/tests/jsnums-test/jsnums-test.js +++ b/tests/jsnums-test/jsnums-test.js @@ -282,7 +282,8 @@ R(["pyret-base/js/js-numbers"], function(JN) { expect(JN.toFixnum(-5, sampleErrbacks)).toBe(-5); expect(JN.toFixnum(0, sampleErrbacks)).toBe(0); expect(JN.toFixnum(3.14, sampleErrbacks)).toBe(3.14); - expect(JN.toFixnum(JN.Rational.makeInstance(355, 113, sampleErrbacks), sampleErrbacks)).toBe(355/113); + expect(JN.toFixnum(JN.Rational.makeInstance(355, 113, sampleErrbacks), sampleErrbacks)) + .toBe(355/113); expect(JN.toFixnum(JN.expt(10, 400, sampleErrbacks), sampleErrbacks)).toBe(Infinity); expect(JN.toFixnum(JN.Roughnum.makeInstance(2.718, sampleErrbacks), sampleErrbacks)).toBe(2.718);