josdejong / mathjs

An extensive math library for JavaScript and Node.js
https://mathjs.org
Apache License 2.0
14.37k stars 1.24k forks source link

Very large numbers #15

Closed limbero closed 10 years ago

limbero commented 11 years ago

It appears that the handling of very large numbers is suboptimal. "5^5^5" (or "5^3125") is evaluated as infinity, for example. I am aware that it's a huge number, "1.911012597945477520356404559703964599198081048 × 10^2184", but still, it seems to me like it should just respond with "Error: Too big number" or something like that. Preferably without breaking the infinity evaluation of things like division with zero.

You'll probably be seeing much of me here since I just built a little something using Math.js here. I might also start contributing to this project in the future. I'd like to take the chance to say that I really love this library and am very grateful for its existence before you start seeing me as an annoying nitpicker. Thanks!

josdejong commented 11 years ago

constructive input is always welcome :)

math.js cannot work with very large numbers as it just uses JavaScript's Numbers, which are floating point numbers, which have a finite range and precision. When out of range, a number will get the value Infinity or -Infinity. That is how floating point numbers work in any language as far as I know.

To be able to work with large numbers, we shouldn't use floating point numbers but use a special BigNumber data type. This feature is planned to be added, see roadmap.

If you really want to give your users an error message "Too big number" instead of returning Infinity, you can of course replace infinity output with an error message.

husayt commented 11 years ago

Here are some other JS Big Number libraries, which could be used for reference:

This is such a major issue, that for time being math.js could just wrap around one of these libraries, until there is dedicated implementation.

josdejong commented 10 years ago

Thanks. Maybe it's just fine to integrate an existing library by wrapping around it? Even easier. (Unless there is a serious performance penalty)

husayt commented 10 years ago

wrapper idea is really interesting. we could have wrappers around standard js number, and some other bigNumber implementations (by using adapter pattern maybe). One could switch them via options. If performance would be a issue for particular problem user could switch to standard js number. Additional plus, this will also make it easier to integrate mathjs's own bignumber implementation in future and still allow users to switch to previous behavior.

diversario commented 10 years ago

MikeMcl's BigNumber library looks rather good, has tests and everything.

josdejong commented 10 years ago

Wohooo, check out the latest develop branch. I've started integrating the bignumber.js library in math.js. See examples and docs.

var mathjs = require('../index'),
    math = mathjs({
      number: {
        defaultType: 'bignumber' // Choose from: 'number' (default), 'bignumber'
      }
    });

math.eval('0.1 + 0.2');  // BigNumber, 0.3
math.eval('0.3 / 0.2');  // BigNumber, 1.5
josdejong commented 10 years ago

A question that arises now is the following: Only arithmetic functions have support for big numbers now. If the expression parser parses all numbers as BigNumber, it will fail on non-arithmetic functions:

var mathjs = require('../index'),
    math = mathjs({
      number: {
        defaultType: 'bignumber' // Choose from: 'number' (default), 'bignumber'
      }
    });

math.eval('0.1 + 0.2');  // BigNumber, 0.3
math.eval('0.3 / 0.2');  // BigNumber, 1.5

math.eval('sin(0.1)');   // ERROR!

How to deal with that?...

husayt commented 10 years ago

Hi Jos, this is terrific news. I am so excited about this and you made a very good choice with BigNumber.js as it is being actively developed.

As for the question, I can a suggest few ideas:

guillermobox commented 10 years ago

Hello,

regarding to this problem of not having the trigonometrical functions available (and possibly others), I think there are possible solutions, similar to the previously commented by husayt.

If you want to implement the second one (which I think is the best one), in the particular case of the trigonometrical functions is very easy. Just take into consideration that the trigonometrical functions have a given periodicity (0, 2*pi), so you should reduce the input argument to a small number. Now, you need a polynomial expansion of the sin function, which you can make yourself using the Taylor Series, for example: http://en.wikipedia.org/wiki/Taylor_series. As long as you can make multiplications and additions, you can represent any function using polynomials. The Taylor series are only one of the multiple possible choices, there are others, with better convergency and other properties.

This will be a little slow, BTW, because instead of calculating a sine, you calculate a (maybe big) polynomial. But don't despair, that's how this things are done in other libraries, along with other techniques to be faster (like look-up table combined with interpolation and series). If you are dealing with arbitrary precission, this is the way to go.

My Summary: implement a slow-but-accurate first solution, wait until a full library call is available in BigNumber and meanwhile tell the customers in the documentation that some functions are going to be slow but accurate (which, BTW, would be the case even if BigNumber implements those functions).

josdejong commented 10 years ago

Yes, in the end all functions should support bignumbers :). Maybe there is a Java or C implementation which we can port to JavaScript.

Until then we need a temporary solution. I think indeed that downgrading to a regular Number is best. When output is a Number instead of a BigNumber you get a clue that the function wasn't able to calculate with high precision.

guillermobox commented 10 years ago

But how can you downgrade a number from BigNumber to a regular Number? In some cases you can (when the number is small), but the range of Number is not enough to take all the cases, like in 5^5^5. So if the user wants to calculate sin(5^5^5), you will now again have the same problem as before: the number is too large.

At least in the case of trigonometrical functions, you can reduce the number to the range of [0, 2*pi] before calculating it, so the problem can be solved using the functions provided for Number. I don't know how much mathematical functions you need that are not implemented in BigNumber, and for those functions for sure you don't have the periodicity trick. So I don't know if you are solving the problem.

josdejong commented 10 years ago

No, we are not solving the problem by downgrading BigNumbers to Numbers for non-supported functions. It is ugly but at least gives us a solution which works: When only using functions that support BigNumber, you get a BigNumber as output, when using unsupported functions you will get a Number as output. BigNumbers which are too large to fit in a Number will return Infinity.

The problem will solved "for real" when all functions support BigNumber, however I'm afraid that this will be quite some work, too much get this done before the next release at least.

guillermobox commented 10 years ago

Oh, sorry, I didn't catch the "return infinity if the number is too large" part before. Yes, that's a good temporary patch, because the implementation of the full mathematical functions is really expensive in terms of time.

josdejong commented 10 years ago

I've released v0.16.0, including BigNumber support! See docs and examples.

We will need to extend BigNumber support to all functions. And It would be great to have a BigComplex and BigUnit types as well in the future.

It's really time to rework the code to a plugin/modular architecture discussed in #71: with this new BigNumber type the amount of manual type checking and type conversion is growing rapidly.