Skip to content

Conversation

ds26gte
Copy link
Contributor

@ds26gte ds26gte commented Sep 20, 2025

This in-progress PR addresses Issue #1812.

A JS test file tests/jsnums-test/jsnums-test.c will test the methods and functions defined in js-numbers.js (Pyret's number library) that cannot be adequately tested by Pyret test files such as tests/pyret/tests/test-numbers.js.

This PR is WIP, and not ready to merge yet.

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.

- ensure integerNthRoot() is monotonically nondecreasing brownplt#1822
- require integerNthRoot() root and radicand to be non-neg
- require nthRoot() root to be non-neg
** should remove fields at .t and above
** should remove any trailing zero fields, also reducing .t
- bnDivide() should call clamp()
- test: bnToString _integerIsZero _integerIsOne _integerGcd
- _integer{Is{Zero,NegativeOne},Modulo,DivideToFixnum,{Greater,Less}Than{,OrEqual}}
- splitIntIntoMantissaExpt
- bnpD{L,R}ShiftTo() should canonicalize
- test for: makeInteger{UnOp,Binop}
- test for: bnpAddTo bnpDMultiply bnpMillerRabin
- test for: bnRemainder bnModPow bnDivideAndRemainder bnIsProbablePrime
@ds26gte
Copy link
Contributor Author

ds26gte commented Sep 29, 2025

tests/jsnums-test/jsnums-test.js now includes 328 (!) tests for the functions defined in src/js/base/js-numbers.js. This includes tests for functions that are either not available to test as Pyret programs, or have some calls that are not exercised by Pyret programs.

While adding tests, found the following corrections that needed to be applied to js-numbers.js:

  • liftFixnumInteger() — despite name, make it accept all fixnums, not just fixnum ints. (This is because it is possible to leak non-int fixnums into this pipeline, and the rational number objects thus created are not only bogus but cause silent errors. Alternative is to make it error on non-ints.)

  • isInteger() — should return false on non-int fixnums. (Alternative is to make it error on non-int fixnums, which doesn't seem right)

  • numerator(), denominator(), remainder(), toStringDigits(), equals(), lessThan(), lessThanOrEqual() — propragate errbacks argument

  • Rational.*.numerator(), Rational.*.denominator(), Roughnum.*.numerator(), Roughnum.*.denominator(), BigInteger.*.numerator(), BigInteger.*.denominator() — should all take errbacks parameter

  • makeIntegerBinop(), makeIntegerUnOp — input & output functions should both take errbacks parameter

  • integerNthRoot() — should error on negative arguments. Initial guess should always be integer, or output is not monotonically non-decreasing

  • nthRoot() — should error on negative argument

  • bnpClamp() — ensure all unwanted fields are deleted, as also all non-significant 0-value fields. (This ensures unique representation for bigintegers — cleaner but also makes equality checks efficient)

  • bnpDLShiftTo(), bnpDRShiftTo(), bnDivide() — should use canonicalizer bnpClamp()

  • Returned module includes _innards field, which gives access to private functions so we can write JS tests for them

  • gcd(), lcm() simplified as we don't need to variadic arguments. Pyret num-gcd (as suggested by @blerner in add gcd operation? are the signatures the way we want? #1427), num-lcm defined

@ds26gte ds26gte self-assigned this Sep 29, 2025
@ds26gte ds26gte requested review from blerner and jpolitz September 29, 2025 13:21
going the fixnum route. (This is because in JS tests, it is possible to call
these methods specifically, and they shouldn't bail unnecessarily.)
- test-numbers.js: confirm num-log(VERYBIGINT) converges
- jsnum-tests.js: confirm log() of VERYBIGINT and VERSMALLRAT converge
- runtime.js charts-lib.js image-lib.js internal-image-types.js internal-image-untyped.js make-image.js: ensure jsnums.{toFixnum,greaterThan,lessThan,roughlyEquals} calls take NumberErrbacks
@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 14, 2025

Made all these changes and some more too. Also checked for and added errbacks to calls in files that import jsnums, viz., runtime.js, charts-lib.js, image-lib.js, internal-image-typed.js, internal-image-untyped.js, make-image.js. Most of the changes in these files have to do with toFixnum(). FYI: code.pyret.org has many such calls, which we have the option of fixing in a code.pyret.org PR.

Also, let me know if we should consider scrubbing the unused HAC functions (which I have identified with a comment). There are 15 of them and they define methods on BigInteger that (a) don't have counterparts in the other boxnums, and (b) aren't used even as BigInteger methods. They are dead code.

@blerner
Copy link
Member

blerner commented Oct 14, 2025

Please don't remove the unused HAC functions yet; if anything that should be a separate PR, but it's low priority at most.

Regarding toFixnum -- why does errbacks propagate through it? Where, if anywhere, does it get used? I tried tracing through this, and I don't see it. Could you therefore fix toFixnum-the-function's signature to pass through a dummy errbacks,

const InternalCompilerErrorErrbacks = {
    ...eachMethodName...: function(arg) { 
        thisRuntime.ffi.throwInternalError("Should never have happened; error was " + arg); 
    }
}

I think a bunch of js-number's exported functions would benefit from this:

  • toFixnum
  • toRational
  • numerator
  • denominator
  • etc, all functions that ought to be infallible, given that they're called on already-constructed valid values

So e.g. toFixnum would become

  var toFixnum = function(n) {
    if (typeof(n) === 'number')
      return n;
    return n.toFixnum(InternalCompilerErrorErrbacks);
  };

Then the call-sites of these functions can stay unchanged and simple, but the internal implementation always has an errbacks to pass through. (This should also change e.g. line 1484) I think this would avoid the need for a matching PR in CPO to change all the callsites of toFixnum, yes?

Speaking of, there are missing errbacks on lines 1919--1942, in the calls to Roughnum.makeInstance -- again, since these ought to be infallible, these should pass through InternalCompilerErrorErrbacks.

@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 14, 2025

Please don't remove the unused HAC functions yet; if anything that should be a separate PR, but it's low priority at most.

Regarding toFixnum -- why does errbacks propagate through it? Where, if anywhere, does it get used? I tried tracing through this, and I don't see it. Could you therefore fix toFixnum-the-function's signature to pass through a dummy errbacks,

const InternalCompilerErrorErrbacks = {
    ...eachMethodName...: function(arg) { 
        thisRuntime.ffi.throwInternalError("Should never have happened; error was " + arg); 
    }
}

I think a bunch of js-number's exported functions would benefit from this:

  • toFixnum
  • toRational
  • numerator
  • denominator
  • etc, all functions that ought to be infallible, given that they're called on already-constructed valid values

So e.g. toFixnum would become

Amen. I'm totally for this, but we seemed to have been biased toward adding errbacks rather than removing it.

Existing code (even before the PR) does however already use errbacks for toFixnum(), even in the code.pyret.org repo! — but not consistently. I'll rewrite toFixnum() in a subsequent push as you suggested, as an explicit errbacks considerably uglifies tons of code.

In this regard, please note that we have arithmetic-function generators makeIntegerUnOp(), makeIntegerBinop(), and makeNumericBinop() whose parameters are functions that take errbacks and which produce functions that take errbacks. However, I see no reason for why many of these functions should take errbacks either (e.g., _integerIsOne()). Making sure they have errbacks triggers the functions that contain calls to them to also have errbacks, making them all ugly too. Should at least some of these function-generators not produce functions that take errbacks? If so, which? Or should the generated functions be dealt with case-by-case?

…invoked

- toFixnum() function doesn't take errbacks
- boxnum toFixnum() methods do
- toFixnum() function passes InternalCompilerErrorErrbacks to boxnum toFixnum() methods
@jpolitz
Copy link
Member

jpolitz commented Oct 14, 2025

Where will InternalCompilerErrbacks be defined and how can it refer to thisRuntime?

…ilerErrorErrbacks to its methods

- 〃 for numerator(), denominator()
- Replace previous use of {} as dummy errbacks with InternalCompilerErrorErrbacks
- Ensure Roughname.makeInstance() calls take errbacks
@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 14, 2025

Here are some functions in js-numbers.js that take errbacks.

makeNumericBinop's args and result both take errbacks
makeIntegerUnOp      ,,
makeNumericBinop    ,,

The following functions dispatch to boxnum methods. They can probably lose their errbacks parameter but call the related boxnum methods with InternalCompilerErrorErrbacks:

add
subtract
multiply
// divide not included, as it is generated using makeNumericBinop() 
equals
equalsAnyZero
eqv
approxEquals
roughlyEquals
roughlyEqualsRel
greaterThanOrEqual
lessThanOrEqual
greaterThan
lessThan
exp
modulo
sqrt
abs
min
floor
ceiling
round
roundEven
log
tan
atan
atan2
cos
sin
acos
asin
integerSqrt
gcd
lcm
quotient
remainder
negate

The following don't correspond to boxnum methods. They should probably
keep their errbacks parameter:

halve
fastExpt
sqr
integerNthRoot
nthRoot
sign
fromFixnum
liftFixnumInteger
fromString
fromSchemeString
toRepeatingDecimal

@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 14, 2025

Where will InternalCompilerErrbacks be defined and how can it refer to thisRuntime?

It can't. Its fields could be mapped to a function like (using @blerner's template):

  function throwInternalCompilerError(msg) {
    throw new Error("Should never have happened; error was " + msg);
  }

@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 15, 2025

While chasing errbacks on {greater,less}Than{,OrEqual}, I noticed that the function versions of these are written like

  // lessThanOrEqual: pyretnum pyretnum -> boolean
  var lessThanOrEqual = function(x, y, errbacks) {
    if(typeof x === "number" && typeof y === "number") {
      return x <= y;
    }
    return makeNumericBinop(undefined, function(x, y, errbacks) {
      return x.lessThanOrEqual(y, errbacks);
    })(x, y, errbacks);
  };

which seems to not take advantage of makeNumericBinop fully. (Viz., the onFixnums effect is done explicitly outside the makeNumericBinop call, and then makeNumericBinop is called with onFixnums = undefined.)

I think it's better simplified as

  // lessThanOrEqual: pyretnum pyretnum -> boolean
  var lessThanOrEqual = makeNumericBinop(
    function(x, y, errbacks) {
      return x <= y;
    },
    function(x, y, errbacks) {
      return x.lessThanOrEqual(y, errbacks);
    },
    {});

This is the case for all four of the number comparison functions. I can't tell what the intent behind the original complication was.

@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 15, 2025

add, subtract, multiply, divide all check if their args are nonbox, if so do something -- call this X --, otherwise they call a "slow" version that is a makeNumericBinop.

The makeNumericBinop does the same nonbox check, if so does onFixnums which is the same as X above, otherwise does onBoxnums. This nonbox check will always be false, so onFixnums can at least be undefined.

I don't know why add, etc., aren't directly defined using makeNumericBinop. Is the speedup because it saves open-codes one function call (i.e., onFixnums())?

@ds26gte
Copy link
Contributor Author

ds26gte commented Oct 15, 2025

P.S. All but one use of makeNumericBinop (there are 9 in total) don't really use the onFixnums version because they're open-coded for "speed". In this case, there is no real need (even in terms of avoiding rewriting) for using makeNumericBinop at all. Let me know.

(The one makeNumericBinop that is used without open-coding part of its functionality is expt.)

(This should probably be addressed in a different PR)

This reverts commit 38b91c6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants