JS Bignum classes
Javascript's native Number type is a double: a 64-bit floating point number. This implies certain limitations in the range of values it can support: it can only hold integers smaller than +/- 2^53 without losing precision, and it can only go up to +/- 2^1024 total. Sometimes, you need more power than that, which means a bignum library.
Currently, the library consists of one class, named Z after the common name for the integers. Z can hold arbitrary-sized integers with perfect precision. Soon I plan to add a Q class for ratios, then likely a C class for complex numbers, and finally might look into either an algebraic number class or a limited (but arbitrarily-high) precision floating-point class.
While I have done decent amounts of work to make this library fast and efficient, I'm sure there's plenty more to do. Pull Requests that can improve speed without drastically killing readability are much appreciated!
The Z class is used for any integer-based calculations.
The Z constructor can be called without new
, like var x = Z(5);
, so it doesn't intrude too much visually. It accepts numbers, strings, or arrays of digits - the latter two can be accompanied with the base they should be interpreted in as a second argument, like Z("1a2", 16)
, or else they'll be assumed to be base 10. It also accepts another Z and produces a clone of it, though this is not the idiomatic way to clone a Z (instead, use Z#clone(), Z#lift(), or Z#adopt(), as appropriate).
All of the basic mathematical operations exist on Z:
"positive"
, this will only return the positive modulus. (Otherwise, Z(-12).mod(10)
will return -2, same as -12 % 10
in normal JS math.)x.mul(x)
- automatically used when you do x.pow(2)
)Note that all of these operations (all the operations, actually) can take any "Z-like" as their argument - Z instances, obviously, but also numbers, string, and arrays of digits. Anything that can be passed to the Z constructor can be used directly as an argument to a Z method.
The basic comparison operations are also defined:
There are several ways to create a new Z out of an existing value:
[1,2,3].map(Z)
, may fail badly as the map() function passes the current index as the second argument, and the constructor will attempt to interpret that as the base argument. Instead, call [1,2,3].map(Z.of)
.)Finally, several utility functions exist:
x.mul(-1)
, clearer and more chainable than calling x.sign *= -1;
.)x.sign
returns 1), false otherwise.x.base
).A Z can also be used directly in expressions with strings or JS numbers - it'll autoconvert itself into the appropriate type. (Possible issue - when concatenated with a string, it appears to trigger .valueOf() first, which means it'll concat "NaN" rather than a string version of the number when the number is large enough. I might just need to remove the .valueOf() entirely.)
Important note: All of the Z instance methods that return a Z do so by mutating the this argument directly, and returning it. That is, in var y = x.mul(3);
not only is y set to x*3, but x itself is now triple what it used to. To avoid this, use the static variants of the methods. (This behavior is absolutely required for high performance, where creating new objects in every operation is unacceptably slow.)
Every method defined on a Z instance also exists on the Z class object, taking an additional first argument. For example, instead of x.mul(y)
, you can call Z.mul(x,y)
. Even .sign
has a static variant in Z.sign(n)
.
The static variants work the same as their instance variants, except that they don't mutate any of their arguments. This is usually accomplished by just cloning the first argument, so most of the static variants imply an extra object creation over their instance variant.
The static variants are often most convenient for just constructing a large number directly, like Z.pow(3, 1000)
(rather than Z(3).pow(1000)
, which I find slightly clumsier to read and write).