Arbitrary-precision Integer, Rational and Float types based on the GMP and MPFR libraries.
npm i gmp-wasm
It can also be used directly from HTML (via jsDelivr):
<!-- loads the full, minified library into the global `gmp` variable --> <script src="https://cdn.jsdelivr.net/npm/gmp-wasm"></script> <!-- or loads the non-minified library --> <script src="https://cdn.jsdelivr.net/npm/gmp-wasm/dist/index.umd.js"></script> <!-- or loads the minified library without Float/MPFR functions --> <script src="https://cdn.jsdelivr.net/npm/gmp-wasm/dist/mini.umd.min.js"></script>
gmp-wasm also provides a high-level wrapper over the GMP functions. There are three major components:
g.Integer()
- Wraps integers (MPZ)g.Rational()
- Wraps rational numbers (MPQ)g.Float()
- Wraps floating-point numbers (MPFR)const gmp = require('gmp-wasm'); gmp.init().then(({ calculate }) => { // calculate() automatically deallocates all objects created within the callback function const result = calculate((g) => { const six = g.Float(1).add(5); return g.Pi().div(six).sin(); // sin(Pi/6) = 0.5 }); console.log(result); });
It is also possible to delay deallocation through the getContext()
API:
const gmp = require('gmp-wasm'); gmp.init().then(({ getContext }) => { const ctx = getContext(); let x = ctx.Integer(1); for (let i = 2; i < 16; i++) { x = x.add(i); } console.log(x.toString()); setTimeout(() => ctx.destroy(), 50); });
The precision and the rounding modes can be set by passing a parameter to the context or to the Float constructor.
const roundingMode = gmp.FloatRoundingMode.ROUND_DOWN; const options = { precisionBits: 10, roundingMode }; const result = calculate(g => g.Float(1).div(3), options); // or const result2 = calculate(g => g.Float(1, options).div(3)); // or const ctx = getContext(options); const result3 = ctx.Float(1).div(3).toString();
High-level wrapper can be combined with low-level functions:
const sum = calculate((g) => { const a = g.Float(1); const b = g.Float(2); const c = g.Float(0); // c = a + b binding.mpfr_add(c.mpfr_t, a.mpfr_t, b.mpfr_t, 0); return c; });
If you want more control and performance you can use the original GMP / MPFR functions even without high-level wrappers.
const gmp = require('gmp-wasm'); gmp.init().then(({ binding }) => { // Create first number and initialize it to 30 const num1Ptr = binding.mpz_t(); binding.mpz_init_set_si(num1Ptr, 30); // Create second number from string. The string needs to be copied into WASM memory const num2Ptr = binding.mpz_t(); const strPtr = binding.malloc_cstr('40'); binding.mpz_init_set_str(num2Ptr, strPtr, 10); // Calculate num1Ptr + num2Ptr, store the result in num1Ptr binding.mpz_add(num1Ptr, num1Ptr, num2Ptr); // Get result as integer console.log(binding.mpz_get_si(num1Ptr)); // Deallocate memory binding.free(strPtr); binding.mpz_clears(num1Ptr, num2Ptr); binding.mpz_t_frees(num1Ptr, num2Ptr); });
Sometimes, it's easier and faster to deallocate everything by reinitializing the WASM bindings:
// Deallocate all memory objects created by gmp-wasm await binding.reset();
In some cases, this library can provide better performance than the built-in BigInt type.
For example, calculating 8000 digits of Pi using the following formula provides better results:
PI = 3 + 3 * (1/2) * (1/3) * (1/4) + 3 * ((1 * 3)/(2 * 4)) * (1/5) * (1 / (4^2)) + 3 * ((1 * 3 * 5) / (2 * 4 * 6)) * (1/7) * (1 / (4^3)) + ...
| Test | Avg. time | Speedup |
| ----------------------------------------------------------------------------------- | --------- | -------- |
| With JS built-in BigInt
type | 129 ms | 1x |
| gmp-wasm Integer()
high-level wrapper | 88 ms | 1.47x |
| Same as previous with delayed memory deallocation | 78 ms | 1.65x |
| gmp-wasm MPZ
low-level functions | 53 ms | 2.43x |
| decimal.js 10.3.1 with integer division | 443 ms | 0.29x |
| big-integer 1.6.51 | 129 ms | 1x |
| ---------------------------- | -------- | -------- |
| gmp-wasm Float()
high-level wrapper | 175 ms | 0.74x |
| Same as previous with delayed memory deallocation | 169 ms | 0.76x |
| gmp-wasm MPFR
low-level functions | 118 ms | 1.09x |
| decimal.js 10.3.1 with float division | 785 ms | 0.16x |
| ---------------------------- | -------- | -------- |
| gmp-wasm Float(1).atan().mul(4)
| 0.6 ms | 215x |
| gmp-wasm Float('0.5').asin().mul(6)
| 17 ms | 7.59x |
* These measurements were made with Node.js v16.14
on an Intel Kaby Lake desktop CPU. Source code is here.