mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

Bignumber floor issue or misunderstanding of relTol ?

Open nycos62 opened this issue 1 year ago • 1 comments

Hello, I don't know well if it's an issue or if I'm doing something wrong : (math.js version 13.0.3)

math.config({
  number: "BigNumber", // Default type of number:
  // 'number' (default), 'BigNumber', or 'Fraction'
  precision: 509, // Number of significant digits for BigNumbers
  relTol: 1e-60,
  absTol: 1e-63
});

//pi*10^60
math.multiply(math.pi, math.pow(math.bignumber(10),math.bignumber(60))).toFixed()
//'3141592653589793238462643383279502884197169399375105820974944.5923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336734'

and then because of relTol of 1e-60 when you floor it it send a wrong result :

math.floor(math.multiply(math.pi, math.pow(math.bignumber(10),math.bignumber(60)))).toFixed()
//'3141592653589793238462643383279502884197169399375105820974945' instead of 
//'3141592653589793238462643383279502884197169399375105820974944'

what should I do to get correct number ? i tried this :

math.config({
  number: "BigNumber", // Default type of number:
  // 'number' (default), 'BigNumber', or 'Fraction'
  precision: 509, // Number of significant digits for BigNumbers
  relTol: 1e-503,
  absTol: 1e-508
});

but it convert then 1e-503 to 0 and then get this error (because relTol = 0 then)

Error: Relative tolerance must be greater than 0

thank you :)

nycos62 avatar Aug 05 '24 11:08 nycos62

That is an interesting one. The relTol and absTol values need to be numbers, and a regular Javascript number can "only" hold an exponent up to about 300, so this limits the number of digits we can handle.

So, using a relTol and absTol in the order of 300 will still work:

math.config({
  number: "BigNumber", // Default type of number:
  // 'number' (default), 'BigNumber', or 'Fraction'
  precision: 309, // Number of significant digits for BigNumbers
  relTol: 1e-300,
  absTol: 1e-303
});

math.floor(math.multiply(math.pi, math.pow(math.bignumber(10),math.bignumber(60)))).toFixed()
// "3141592653589793238462643383279502884197169399375105820974944" (as expected)

To address this, I think we can either implement support for relTol and absTol to be a BigNumber, or change them to only note the number of digits (like 300) instead of the value 1e-300.

josdejong avatar Aug 21 '24 12:08 josdejong

( Hello, just a friendly reminder, still the problem in v14 for relTol > 300 )

nycos62 avatar Nov 22 '24 21:11 nycos62

Yes. Help addressing this would be very welcome :).

josdejong avatar Nov 27 '24 13:11 josdejong

I mean, I think the problem is worse/more urgent than this: enter floor(bignumber("12345678901234567890.5")).toFixed() in the parser on the mathjs.org page and it displays "12345678901234567891", which is clearly not the floor of that BigNumber, and we all know that the default BigNumber implementation has plenty of precision to distinguish 12345678901234567890.5 from an integer, so it should know to round down.

gwhitney avatar Jan 28 '25 04:01 gwhitney

Ugh, the fun doesn't stop there: try floor(1234567890123.5).toFixed() and you get "1234567890124", which is clearly not the floor of 123456890123.5, and JavaScript number has enough precision to distinguish 1234567890123.5 from an integer. I understand the rationale for having relTol and absTol but either they are poorly set or mathjs is currently using them too zealously, making it produce clearly mathematically wrong answers that JavaScript Math.floor gets correct. That's a bad look for mathjs. I will reflect on this...

gwhitney avatar Jan 28 '25 04:01 gwhitney

Ah, maybe there is a non-painful way out: letting b = 1234567890123.5, and its actual floor f and its actual ceil c, the issue is that b rounds to c and by the tolerance is equal to c, so floor returns c. But it is just as equal to f and f < c, so in the case of floor it should return f. I think by changing this logic in floor and ceil to check both candidates and if they are both == take the appropriate one, we can make floor and ceil work intuitively correctly without having to face rejiggering the comparison system, which would be a real bear.

gwhitney avatar Jan 28 '25 04:01 gwhitney

To be more explicit, here's my exact proposal: for taking the floor of a value, there are two candidates: its "native" floor f and its "native" round r, both of them ignoring reltol/abstol. If f = r, return that. If not, presumably f < r. So we'd like to return f unless we think we're essentially equal to r and would spuriously be going down to f. So if we are nearlyEqual to r but not nearlyEqual to f, return r. Otherwise, again return f.

Similar logic for ceil, just using the native round and native ceil.

How does that sound? I will try it in practice and see how it does with unit tests, and I will add unit tests for some of the cases here. If that all goes smoothly and no objections/concerns are raised here, I will submit a PR.

gwhitney avatar Jan 28 '25 06:01 gwhitney

Hello Glen,

as I understood, you always HAVE TO explicitely specify the configuration of relTol / absTol / precision, when you are working with bignumbers :

math.config({
  number: "BigNumber",
  precision: 309,  
  absTol: 1e-308,
  relTol: 1e-305
});
math.floor(math.bignumber("12345678901234567890.5")).toFixed();
//=> '12345678901234567890'

the problem appear when math.config.precision go over the max capacity of the type of math.config.relTol or math.config.absTol which is 1e-320

nycos62 avatar Jan 28 '25 13:01 nycos62

My belief is that the default configuration of mathjs should not cause it to make obviously wrong calculations of the floor of not just bignumbers but also of just regular numbers. I will be trying out the new suggested algorithm and see where we land.

gwhitney avatar Jan 28 '25 15:01 gwhitney

for instance, if you look to compute continued fraction of a real number you can't go deeper than 320 digits because of this limitation 1e-320

nycos62 avatar Jan 28 '25 15:01 nycos62

There are three things going on here: (1) the true underlying precision of the representation, which is 53 bits of mantissa and 11 bits of exponent for JavaScript number and can be set arbitrarily for BigNumber, (2) the "fuzz" we apply via relTol and absTol to be cautious in claiming that two floating point numbers are distinct given the limitations in (1), and (3) the way we apply/interpret that fuzz in arriving at the results of computations. Right now there are problems in both (2) and (3):

(2) We are using number to encode the fuzz as a multiplicative constant, but the precision of number is insufficient for measuring a reasonable amount of fuzz when you are using very high-precision BigNumbers. The mathjs package should surely switch to some selection of (a) having different tolerance constants for BigNumbers, perhaps BigNumbers themselves, (b) by default auto-generating tolerance values for BigNumbers based on the BigNumbers' self-reported precision, (c) measuring tolerance on a logarithmic scale.

(3) One issue is that we assume (in places like floor and ceil) that if a number is within its tolerance of its rounded value, then for practical purposes it is that integer. But that's not appropriate; if it is within its tolerance of its floored value as well, then it is no more likely to be its rounded value than its floored value, so we might as well use its floored value for what we return from the floor function in that case.

My plan is to soon submit a PR for just that specific example of (3) in response to this issue. That will not do anything for issue (2) but this has been brought up already in #2608, so we will leave that issue open for possible future improvements in (2) -- and possibly other instances of (3) than the one we are going to address with the upcoming PR.

When we are all done, it will be a reasonable aspiration that you should be able to use mathjs to calculate the correct continued fraction for the circle constant τ to as many places as you have the processor power and patience for.

gwhitney avatar Jan 28 '25 20:01 gwhitney