numberscope / frontscope

Numberscope's front end and user interface: responsible for specifying sequences and defining and displaying visualizers
MIT License
7 stars 15 forks source link

Update mathjs to get bigint support #343

Open gwhitney opened 5 months ago

gwhitney commented 5 months ago

PR https://github.com/josdejong/mathjs/pull/3207 adds bigint support to mathjs. Once the Delft project is merged, update to a version of mathjs that includes that support, and remove code in frontscope working around mathjs' lack of bigint support.

gwhitney commented 5 months ago

In fact, any version of mathjs from 13.0 on will have bigint support.

gwhitney commented 4 months ago

Kate brought up a rounding issue in a Delft-related discussion:

sequence accepts n^7.1 as well-formed but I'm not sure what it means. It looks (in difference visualizer) as if it returns integers -- probably rounded from n^(7.1). If it's rounding all non-integer inputs, should that be indicated somehow?

Yes, indeed, we are explicitly taking the Math.floor of any finite output from mathjs before converting it into a bigint. But we should revisit the details of how rounding works when we switch over to the new mathjs.

katestange commented 4 months ago

I'm not sure if this is the right place for this bug report, but a bigint issue appears as follows, so this is something to test when this gets fixed. The following specimen fails when the integer produced by formula becomes too big:

http://localhost:5173/?name=FormulaTurtleSevenPower&viz=Turtle&domain=0+1+2+3&range=30+45+60+90&stepSize=80&strokeWeight=2&bgColor=c21919&strokeColor=764141&seq=Formula&formula=n%5E7%254

That uses formula n^7%4. By contrast, trying a different sequence mod(mod(n,4)^7,4) will allow the visualizer to work properly and continue, since it never veers into bigints.

gwhitney commented 2 weeks ago

There is a critical issue that will come up when we switch to a version of mathjs with bigint support: Presumably when we do so, then all numerical constants in the formula will be interpreted as bigints. When that's the case, how would we like the formula sqrt(2)n to behave? The bigint 2n has no square root among bigints. The current default behavior of mathjs is to return the result of sqrt(2) (when 2 is a bigint) as the JavaScript (IEEE double) number 1.414213.... But then if n is a bigint larger than MAX_SAFE_INTEGER, mathjs will throw an error when it tries to multiply the result of sqrt(2) by n, because it knows that it will surely lose precision and return the mathematically incorrect value. So, do we want to:

(a) Swap sqrt for a function that only returns bigints, say the bigint that is the floor of the square root of its argument? Then sqrt(2) will be 1n and you won't be able to use this formula in the Beatty sequence in its ordinary way; you will have to write sqrt(2*n^2) to get what you wanted. Knowing and adjusting to this convention might be an unintuitive "gotcha" for users.

(b) Choose some arbitrary specific precision, like say 100 decimal places, and have mathjs always compute real-valued functions to that precision, but throw errors in operations that may nevertheless return erroneous integer parts of results because of the magnitude of another operand? Once we allow OEIS sequences in formulas, sqrt(2)Axxxxxx(n) will still definitely throw errors for some sequences Axxxxxx, because there are really huge entries in the OEIS.

(c) Try to arrange mathjs to defer its choice of precision when computing real-valued functions until it has analyzed the whole formula, and then use a precision sufficient to guarantee that the result it produces will have the same floor() as the mathematically exact answer? In our running example, it will need to determine the magnitude of n before computing sqrt(2), and then get the value of sqrt(2) to (I am guessing) one part in 2n before it does the multiplication. But then if this multiplication is more deeply embedded, e.g. (sqrt(2)n)^3, it may need much more precision than this to get the answer correct "within floor()". This may be a great deal of work; I am sure that mathjs has no such facility at the moment, and I am not quite sure how to accomplish this. Is it something that "interval arithmetic" can handle? If so, it might mean implementing an interval arithmetic data type for mathjs, which could be a major undertaking...

(d) Try to arrange mathjs to perform formula transforms to make an input formula "safe" for bigint computation. In other words, sqrt(2)n would automatically be converted to sqrt(2n^2) for you, and (sqrt(2)n)^3 would be converted to sqrt((2n^2)^3). At least mathjs already has a notion of formula transform, although it doesn't have one already existing for this purpose. And again, I don't know how much work this could be for extensive coverage of mathjs's library of functions. I don't know how one could possibly transform sin(1)n, so the coverage may be quite limited in any case

(e) Something else?

It seems to me there is no obvious good method here, but perhaps I am not thinking clearly. So I would appreciate suggestions @katestange / @Vectornaut , and I am marking this for a future meeting.

gwhitney commented 2 weeks ago

Implementing interval arithmetic would not be a significant burden. But it doesn't seem to be the whole answer here. We want to analyze the formula sqrt(2)n and determine, for a given n, what interval we need to constrain sqrt(2) to in order to obtain an interval for sqrt(2)n that does not contain an integer. Do either of you know a framework or references we could look up for doing this (by "this", I mean do such an analysis for an arbitrary formula based on its abstract tree of operations)? All I can think of off the top of my head is to try some reasonable guess for the constraint on sqrt(2), say [√2 - 1/(2n), √2 + 1/(2n)] roughly speaking, and do the computation, and see if the answer contains no whole number. If it does, look at the size of the resulting interval, and use that somehow to update the guess on the needed precision and iterate... Do either of you think there is a better way?