From 10ba82d0751f835c2322272aeeeca49af1c36d90 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 25 Aug 2025 15:14:44 -0400 Subject: [PATCH 1/2] 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/2] 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); } };