From 10ba82d0751f835c2322272aeeeca49af1c36d90 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 25 Aug 2025 15:14:44 -0400 Subject: [PATCH 1/5] fromFixnum(): fix for argument fixnum is less than about 1e-7, brownplt/code.pyret.org#556 --- src/js/base/js-numbers.js | 29 +++++++++++++++++++++++++---- tests/pyret/tests/test-json.arr | 3 +++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index bd0fe9523d..515d9e946b 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -173,17 +173,36 @@ define("pyret-base/js/js-numbers", function() { } else { // used to return float, now rational var stringRep = x.toString(); - var match = stringRep.match(/^(.*)\.(.*)$/); + var match = stringRep.match(genScientificPattern); + var factor1 = 1; + if (match) { + var divideP = false; + stringRep = match[1]; + var exponentPart = match[2]; + if (exponentPart.match('^-')) { + divideP = true; + exponentPart = exponentPart.substring(1); + } + var exponentValue = makeBignum("1" + zfill(Number(exponentPart))); + if (divideP) { + factor1 = divide(1, exponentValue); + } + else { + factor1 = exponentValue; + } + } + match = stringRep.match(/^(.*)\.(.*)$/); + var factor2; 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); + factor2 = Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks); } else { - return Rational.makeInstance(x, 1, errbacks); + factor2 = Rational.makeInstance(Number(stringRep), 1, errbacks); } - + return multiply(factor1, factor2); } }; @@ -2036,6 +2055,8 @@ 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) { if (x.match(digitRegexp)) { diff --git a/tests/pyret/tests/test-json.arr b/tests/pyret/tests/test-json.arr index b05f14d258..3e5fb46ae1 100644 --- a/tests/pyret/tests/test-json.arr +++ b/tests/pyret/tests/test-json.arr @@ -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": From 92aee48168c2f236c750e5baf8498892af631949 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sat, 30 Aug 2025 02:52:57 -0400 Subject: [PATCH 2/5] fromFixnum() simplified, with better var names, and comments --- src/js/base/js-numbers.js | 60 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 515d9e946b..971c43eca2 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -173,36 +173,48 @@ define("pyret-base/js/js-numbers", function() { } else { // used to return float, now rational var stringRep = x.toString(); - var match = stringRep.match(genScientificPattern); - var factor1 = 1; - if (match) { - var divideP = false; - stringRep = match[1]; - var exponentPart = match[2]; + var exponentMatch = stringRep.match(genScientificPattern); + var exponentFactor = 1; + if (exponentMatch) { + // x is in scientific notation -- i.e. it has an E; + // find the exponent part, and change x to just the base part; + // calculate exponentFactor, which is the integer 10^exponent + var divideP = false; // divideP==true iff exponent is negative + stringRep = exponentMatch[1]; + x = Number(stringRep); + var exponentPart = exponentMatch[2]; if (exponentPart.match('^-')) { divideP = true; exponentPart = exponentPart.substring(1); } - var exponentValue = makeBignum("1" + zfill(Number(exponentPart))); - if (divideP) { - factor1 = divide(1, exponentValue); - } - else { - factor1 = exponentValue; - } - } - match = stringRep.match(/^(.*)\.(.*)$/); - var factor2; - if (match) { - var afterDecimal = parseInt(match[2]); - var factorToInt = Math.pow(10, match[2].length); - var extraFactor = _integerGcd(factorToInt, afterDecimal); - var multFactor = factorToInt / extraFactor; - factor2 = Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks); + exponentFactor = makeBignum("1" + zfill(Number(exponentPart))); + if (divideP) exponentFactor = divide(1, exponentFactor); + } + var decimalMatch = stringRep.match(/^.*\.(.*)$/); + var baseFactor; + if (decimalMatch) { + // we convert the after-decimal part to + // afterDecimalNumerator / afterDecimalDenominator + // where these are guaranteed integers + var afterDecimalNumerator = parseInt(decimalMatch[1]); + var afterDecimalDenominator = Math.pow(10, decimalMatch[1].length); + // x can now be the vulgar fraction + // (x * afterDecimalDenominator) / afterDecimalDenominator + // since x * afterDecimalDenominator is guaranteed to be an integer; + // however, we can simplify this multiplier; + // first find gcd(afterDecimalNumerator, afterDecimalDenominator) + var afterDecimalGCD = _integerGcd(afterDecimalNumerator, afterDecimalDenominator); + // the mulitplier is afterDecimalDenominator / afterDecimalGCD + var simplifiedMultipler = afterDecimalDenominator / afterDecimalGCD; + // multiply x by simplifiedMultipler to get an (integer) numerator; + // simplifiedMultipler itself is the denominator + baseFactor = Rational.makeInstance(Math.round(x * simplifiedMultipler), + Math.round(simplifiedMultipler), errbacks); } else { - factor2 = Rational.makeInstance(Number(stringRep), 1, errbacks); + // x is already integer + baseFactor = Rational.makeInstance(x, 1, errbacks); } - return multiply(factor1, factor2); + return multiply(exponentFactor, baseFactor); } }; From 11cbc5c2122cfec694eb1296080803881278d18a Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sun, 14 Sep 2025 00:38:21 -0400 Subject: [PATCH 3/5] - define fromFixnum() in terms of fromString() - improve fromString() to use itself recursively rather than makeBignum() --- src/js/base/js-numbers.js | 65 +++------------------------------------ 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 971c43eca2..3112dfe7d6 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -160,62 +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 exponentMatch = stringRep.match(genScientificPattern); - var exponentFactor = 1; - if (exponentMatch) { - // x is in scientific notation -- i.e. it has an E; - // find the exponent part, and change x to just the base part; - // calculate exponentFactor, which is the integer 10^exponent - var divideP = false; // divideP==true iff exponent is negative - stringRep = exponentMatch[1]; - x = Number(stringRep); - var exponentPart = exponentMatch[2]; - if (exponentPart.match('^-')) { - divideP = true; - exponentPart = exponentPart.substring(1); - } - exponentFactor = makeBignum("1" + zfill(Number(exponentPart))); - if (divideP) exponentFactor = divide(1, exponentFactor); - } - var decimalMatch = stringRep.match(/^.*\.(.*)$/); - var baseFactor; - if (decimalMatch) { - // we convert the after-decimal part to - // afterDecimalNumerator / afterDecimalDenominator - // where these are guaranteed integers - var afterDecimalNumerator = parseInt(decimalMatch[1]); - var afterDecimalDenominator = Math.pow(10, decimalMatch[1].length); - // x can now be the vulgar fraction - // (x * afterDecimalDenominator) / afterDecimalDenominator - // since x * afterDecimalDenominator is guaranteed to be an integer; - // however, we can simplify this multiplier; - // first find gcd(afterDecimalNumerator, afterDecimalDenominator) - var afterDecimalGCD = _integerGcd(afterDecimalNumerator, afterDecimalDenominator); - // the mulitplier is afterDecimalDenominator / afterDecimalGCD - var simplifiedMultipler = afterDecimalDenominator / afterDecimalGCD; - // multiply x by simplifiedMultipler to get an (integer) numerator; - // simplifiedMultipler itself is the denominator - baseFactor = Rational.makeInstance(Math.round(x * simplifiedMultipler), - Math.round(simplifiedMultipler), errbacks); - } else { - // x is already integer - baseFactor = Rational.makeInstance(x, 1, errbacks); - } - return multiply(exponentFactor, baseFactor); - } + return fromString(String(x), errbacks); }; var expandExponent = function(s) { @@ -2093,7 +2038,7 @@ define("pyret-base/js/js-numbers", function() { var beforeDecimalString = aMatch[2]; var beforeDecimal = 0; if (beforeDecimalString !== '') { - beforeDecimal = makeBignum(beforeDecimalString); + beforeDecimal = fromString(beforeDecimalString); } // var afterDecimalString = aMatch[3]; @@ -2101,9 +2046,9 @@ define("pyret-base/js/js-numbers", function() { 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); } } // @@ -2117,7 +2062,7 @@ 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')); } var finalDen = denominatorTen; From 777d1184ded367370572736610ab6925f7f8315a Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 02:24:30 -0400 Subject: [PATCH 4/5] scrub regexp no longer used --- src/js/base/js-numbers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d7ef80bc73..69a9da9ae3 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2039,11 +2039,8 @@ define("pyret-base/js/js-numbers", function() { var roughnumRatRegexp = new RegExp("^~([+-]?\\d+)/(\\d+)$"); - 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) { if (x.match(digitRegexp)) { From b3311dba189431a95937fe6f59051088cf189f88 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 02:38:58 -0400 Subject: [PATCH 5/5] revert change to fromString() that replaces makeBignum() calls with recursive fromString() calls --- src/js/base/js-numbers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 69a9da9ae3..b2857cf35d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2065,7 +2065,7 @@ define("pyret-base/js/js-numbers", function() { var beforeDecimalString = aMatch[2]; var beforeDecimal = 0; if (beforeDecimalString !== '') { - beforeDecimal = fromString(beforeDecimalString); + beforeDecimal = makeBignum(beforeDecimalString); } // var afterDecimalString = aMatch[3]; @@ -2073,9 +2073,9 @@ define("pyret-base/js/js-numbers", function() { var afterDecimal = 0; if (afterDecimalString !== '') { afterDecimalString = afterDecimalString.substring(1); - denominatorTen = fromString('1' + new Array(afterDecimalString.length + 1).join('0')); + denominatorTen = makeBignum('1' + new Array(afterDecimalString.length + 1).join('0')); if (afterDecimalString !== '') { - afterDecimal = fromString(afterDecimalString); + afterDecimal = makeBignum(afterDecimalString); } } // @@ -2089,7 +2089,7 @@ define("pyret-base/js/js-numbers", function() { if (exponentSign === '-' || exponentSign === '+') { exponentString = exponentString.substring(1); } - exponent = fromString('1' + new Array(Number(exponentString) + 1).join('0')); + exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0')); } var finalDen = denominatorTen;