Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
f3a3930
Added tests/jsnum-test.js to test methods in js-numbers.js that aren'…
ds26gte Sep 4, 2025
b414392
Merge branch 'fromFixnum-fix' into jsnum-tests
ds26gte Sep 15, 2025
a6485eb
- jsnums-test.js: enhance #1812
ds26gte Sep 16, 2025
812e409
fromString(): Rational.makeInstance already takes care of den == 1
ds26gte Sep 16, 2025
a1837be
- makeNumericBinop tests
ds26gte Sep 16, 2025
f2d40dd
jsnums-tests.js: Add tests for Rational methods
ds26gte Sep 17, 2025
b8442a2
- more Rational.* tests
ds26gte Sep 19, 2025
6e0e0ef
scrub unnecessary defs from jsnums-test.js
ds26gte Sep 19, 2025
74efc26
add Roughnum.* tests
ds26gte Sep 19, 2025
bc4f135
- add arrayEquals to help test structural equality
ds26gte Sep 19, 2025
6beef5f
- numerator, denominator methods take errbacks param
ds26gte Sep 19, 2025
66122ae
Merge branch 'horizon' into jsnum-tests
ds26gte Sep 20, 2025
cc52770
jsnums-test.js: add test for gcd, lcm
ds26gte Sep 20, 2025
cb4e69e
- add tests for number predicates, sign, zfill, liftFixnumInteger
ds26gte Sep 21, 2025
1b7a85e
add tests for number casts
ds26gte Sep 21, 2025
e791660
- add tests for nthRoot() and integerNthRoot() #1812
ds26gte Sep 22, 2025
8c85b1f
- BigInteger canonicalizer bnpClamp() #1823
ds26gte Sep 23, 2025
74b589c
- test that bignums indeed have unique representations
ds26gte Sep 24, 2025
8d75cd8
tests for
ds26gte Sep 24, 2025
3b9cad4
- test BigInteger's {copy,sub,multiply,{d,}{l,r}Shift}To
ds26gte Sep 25, 2025
cb5b948
test for bnp{Squareto,DivRemTo,Exp,IsEven,ModInt}
ds26gte Sep 25, 2025
15914a9
- makeInteger{UnOp,Binop}: use & propagate errbacks appropriately
ds26gte Sep 26, 2025
09a47af
test bnpToRadix
ds26gte Sep 29, 2025
d727abd
- ensure all calls to equals(), lessThan{,OrEqual}() take errbacks
ds26gte Sep 29, 2025
0bdd499
test toRepeatingDecimal() for non-valid args
ds26gte Sep 29, 2025
00b90b9
- add num-gcd courtesy @blerner #1427
ds26gte Oct 3, 2025
d3d00ad
remove all (commented) debugging console.log's
ds26gte Oct 7, 2025
1ec7a95
jsnums-test.js: Add test for toRepeatingDecimal() mistakenly called with
ds26gte Oct 7, 2025
7b63e0f
- lift def of getReside() up from toRepeatingDecimal()
ds26gte Oct 8, 2025
2a22911
simplify toRepeatingDecimal(). Now that getResidue() is out,
ds26gte Oct 9, 2025
d58e838
jsnums-test.js: test toRepeatingDecimal errors, with and without corr…
ds26gte Oct 9, 2025
298e87c
Following's defs & calls need explicit errbacks propagration:
ds26gte Oct 13, 2025
b952582
Following defined but unused -- identify for now, potentially remove …
ds26gte Oct 13, 2025
e962b78
Don't α-rename potential unusued functions
ds26gte Oct 13, 2025
e29c32b
`make test` should run tests/jsnums-test/jsnums-test.js
ds26gte Oct 13, 2025
486dd9c
arrayEquals() not needed in jsnums-test.js
ds26gte Oct 13, 2025
3c0f18f
function and method isInteger() call should take errbacks
ds26gte Oct 13, 2025
66d11d2
- Ensure function and method toFixnum() calls take errbacks
ds26gte Oct 13, 2025
1e5c790
Merge branch 'horizon' into jsnum-tests
ds26gte Oct 13, 2025
b8e6c6f
add tests that liftFixnumInteger on non-int fixnum produces valid rat…
ds26gte Oct 13, 2025
0ddee45
- {Rational,BigInteger}.log() should take advantage of log() instead of
ds26gte Oct 13, 2025
d489ce1
add errbacks to stray add multiply subtract expt
ds26gte Oct 13, 2025
f2663ca
ensure negate() gets an errbacks arg
ds26gte Oct 13, 2025
4e104da
ensure floor() calls get errbacks
ds26gte Oct 13, 2025
8606db2
ensure numerator() calls get errbacks
ds26gte Oct 13, 2025
70a7be6
ensure abs() sign() sqrt() get errbacks arg
ds26gte Oct 14, 2025
2b840c3
ensure liftFixnumInteger() calls get errbacks arg
ds26gte Oct 14, 2025
551610f
- isInteger() - don't take errbacks as it's standard JS!
ds26gte Oct 14, 2025
79314f8
- define InternalCompilerErrorErrbacks for errbaks that shouldn't be …
ds26gte Oct 14, 2025
3c6f202
- Function toRational() doesn't take errbacks but passes InternalComp…
ds26gte Oct 14, 2025
605dee2
Function toRoughnum() doesn't take errbacks but passes InternalCompil…
ds26gte Oct 14, 2025
fbbccc3
throwInternalCompilerError() uses throw new Error()
ds26gte Oct 14, 2025
a95a176
- toFixnum() -- check if arg is (box)number before trying method
ds26gte Oct 14, 2025
94d209d
- remove errbacks param from: bnpExp bnModPowInt bnPow bnModInverse
ds26gte Oct 14, 2025
671d80e
No errbacks param for: abs equalsAnyZero floor ceiling round roundEven
ds26gte Oct 15, 2025
38b91c6
Simplify defs of {greater,less}Than{,OrEqual}
ds26gte Oct 15, 2025
89c8c37
Ensure sqr() calls take errbacks
ds26gte Oct 15, 2025
0fac202
BitInteger.prototype.sqrt def doesn't need to be wrapped in an IIFE
ds26gte Oct 15, 2025
190468d
Revert "Simplify defs of {greater,less}Than{,OrEqual}"
ds26gte Oct 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 34 additions & 30 deletions src/js/base/js-numbers.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,31 +160,7 @@ define("pyret-base/js/js-numbers", function() {

// fromFixnum: fixnum -> pyretnum
var fromFixnum = function(x, errbacks) {
if (!isFinite(x)) {
return Roughnum.makeInstance(x, errbacks);
}
var nf = Math.floor(x);
if (nf === x) {
if (isOverflow(nf)) {
return makeBignum(expandExponent(x+''));
} else {
return nf;
}
} else {
// used to return float, now rational
var stringRep = x.toString();
var match = stringRep.match(/^(.*)\.(.*)$/);
if (match) {
var afterDecimal = parseInt(match[2]);
var factorToInt = Math.pow(10, match[2].length);
var extraFactor = _integerGcd(factorToInt, afterDecimal);
var multFactor = factorToInt / extraFactor;
return Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks);
} else {
return Rational.makeInstance(x, 1, errbacks);
}

}
return fromString(String(x), errbacks);
};

var expandExponent = function(s) {
Expand Down Expand Up @@ -1225,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);
});

Expand Down Expand Up @@ -1477,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);

Expand Down Expand Up @@ -2066,8 +2045,11 @@ define("pyret-base/js/js-numbers", function() {

var scientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+]?\\d+)$");

var genScientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+-]?\\d+)$");

// 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)) {
Expand All @@ -2090,19 +2072,22 @@ define("pyret-base/js/js-numbers", function() {
var beforeDecimalString = aMatch[2];
var beforeDecimal = 0;
if (beforeDecimalString !== '') {
beforeDecimal = makeBignum(beforeDecimalString);
beforeDecimal = fromString(beforeDecimalString);
}
// console.log('beforeDecimal =', beforeDecimal);
//
var afterDecimalString = aMatch[3];
var denominatorTen = 1;
var afterDecimal = 0;
if (afterDecimalString !== '') {
afterDecimalString = afterDecimalString.substring(1);
denominatorTen = makeBignum('1' + new Array(afterDecimalString.length + 1).join('0'));
denominatorTen = fromString('1' + new Array(afterDecimalString.length + 1).join('0'));
if (afterDecimalString !== '') {
afterDecimal = makeBignum(afterDecimalString);
afterDecimal = fromString(afterDecimalString);
}
}
// console.log('afterDecimal =', afterDecimal);
// console.log('denominatorTen =', denominatorTen);
//
var exponentString = aMatch[4];
var exponentNegativeP = false;
Expand All @@ -2114,23 +2099,38 @@ define("pyret-base/js/js-numbers", function() {
if (exponentSign === '-' || exponentSign === '+') {
exponentString = exponentString.substring(1);
}
exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0'));
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);
Expand Down Expand Up @@ -2418,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);
Expand Down Expand Up @@ -3056,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);
Expand Down Expand Up @@ -3644,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);
};

Expand Down
156 changes: 156 additions & 0 deletions tests/jsnums-test/jsnums-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// 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 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:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ds26gte why are these commented out? We should be testing to make sure they fail, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that they should not fail, but were. I kept them around so I could solve the underlying problem as to why they were failing.

Now that I've canonicalized bigint representation -- i.e, every bigint now has a only one representation, not multiple as before --, these tests no longer fail, and are now uncommented.

// 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
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');
});

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
// 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);

// 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);

// 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();

});
3 changes: 3 additions & 0 deletions tests/pyret/tests/test-json.arr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ check "conversion":
p('[5, null, {"hello": "world"}]') is
J.j-arr([list: J.j-num(5), J.j-null,
J.j-obj([SD.string-dict: "hello", J.j-str("world")])])

p('1E-7').native() is 1e-7
p('5E-19').native() is 5e-19
end

check "native":
Expand Down