GitHub image
gmp-wasm
⌘K

GMP-WASM

npm package codecov Build status JSDelivr downloads

Arbitrary-precision Integer, Rational and Float types based on the GMP and MPFR libraries.

Features

  • Supports all modern browsers, web workers, Node.js and Deno
  • Includes an easy-to-use, high-level wrapper, but low-level functions are also exposed
  • Has a lot more features, and in some cases, it's faster than the built-in BigInt type
  • The WASM binary is bundled as a compressed base64 string (no problems with linking)
  • Works even without Webpack or other bundlers
  • Includes TypeScript type definitions, check API here.
  • Zero dependencies
  • Full minified and gzipped bundle has a size of Bundle size
  • It also packages a mini bundle without Float/MPFR operations Bundle size
  • 100% open source & transparent build process

Installation

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>

Usage

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();

Predefined constants

  • Pi
  • EulerConstant
  • EulerNumber
  • Log2
  • Catalan

Advanced usage

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();

Performance

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.