Skip to content

Commit

Permalink
feat: implement support for bigint (#3207, #2737)
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong authored May 31, 2024
1 parent 1f05a35 commit ab3461d
Show file tree
Hide file tree
Showing 143 changed files with 1,570 additions and 262 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Math.js is an extensive math library for JavaScript and Node.js. It features a f

## Features

- Supports numbers, big numbers, complex numbers, fractions, units, strings, arrays, and matrices.
- Supports numbers, bignumbers, bigints, complex numbers, fractions, units, strings, arrays, and matrices.
- Is compatible with JavaScript's built-in Math library.
- Contains a flexible expression parser.
- Does symbolic computation.
Expand Down Expand Up @@ -118,13 +118,13 @@ The code of `mathjs` is written in ES modules, and requires all files to have a

What mathjs tries to achieve is to offer an environment where you can do calculations with mixed data types,
like multiplying a regular `number` with a `Complex` number or a `BigNumber`, and work with all of those in matrices.
Mathjs also allows to add a new data type, like say `BigInt`, with little effort.
Mathjs also allows to add a new data type with little effort.

The solution that mathjs uses has two main ingredients:

- **Typed functions**. All functions are created using [`typed-function`](https://github.com/josdejong/typed-function/). This makes it easier to (dynamically) create and extend a single function with new data types, automatically do type conversions on function inputs, etc. So, if you create function multiply for two `number`s, you can extend it with support for multiplying two `BigInts`. If you define a conversion from `BigInt` to `number`, the typed-function will automatically allow you to multiply a `BigInt` with a `number`.
- **Typed functions**. All functions are created using [`typed-function`](https://github.com/josdejong/typed-function/). This makes it easier to (dynamically) create and extend a single function with new data types, automatically do type conversions on function inputs, etc. So, if you create function multiply for two `number`s, you can extend it with support for multiplying your own data type, say `MyDecimal`. If you define a conversion from `MyDecimal` to `number`, the typed-function will automatically allow you to multiply a `MyDecimal` with a `number`.

- **Dependency injection**. When we have a function `multiply` with support for `BigInt`, thanks to the dependency injection, other functions using `multiply` under the hood, like `prod`, will automatically support `BigInt` too. This also works the other way around: if you don't need the heavyweight `multiply` (which supports BigNumbers, matrices, etc), and you just need a plain and simple number support, you can use a lightweight implementation of `multiply` just for numbers, and inject that in `prod` and other functions.
- **Dependency injection**. When we have a function `multiply` with support for `MyDecimal`, thanks to the dependency injection, other functions using `multiply` under the hood, like `prod`, will automatically support `MyDecimal` too. This also works the other way around: if you don't need the heavyweight `multiply` (which supports BigNumbers, matrices, etc), and you just need a plain and simple number support, you can use a lightweight implementation of `multiply` just for numbers, and inject that in `prod` and other functions.

At the lowest level, mathjs has immutable factory functions which create immutable functions. The core function `math.create(...)` creates a new instance having functions created from all passed factory functions. A mathjs instance is a collection of created functions. It contains a function like `math.import` to allow extending the instance with new functions, which can then be used in the expression parser.

Expand Down
38 changes: 23 additions & 15 deletions docs/core/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,29 @@ The following configuration options are available:
determined by the option `matrix`. In case of mixed matrix
inputs, a matrix will be returned always.

- `number`. The type of numeric output for functions which cannot
determine the numeric type from the inputs. For most functions though,
the type of output is determined from the the input:
a number as input will return a number as output,
a BigNumber as input returns a BigNumber as output.

For example the functions `math.evaluate('2+3')`, `math.parse('2+3')`,
`math.range('1:10')`, and `math.unit('5cm')` use the `number` configuration
setting. But `math.sqrt(4)` will always return the number `2`
regardless of the `number` configuration, because the input is a number.

Available values are: `'number'` (default), `'BigNumber'`, or `'Fraction'`.
[BigNumbers](../datatypes/bignumbers.js) have higher precision than the default
numbers of JavaScript, and [`Fractions`](../datatypes/fractions.js) store
values in terms of a numerator and denominator.
- `number`. The type used to parse strings into a numeric value or create a new
numeric value internally.

For most functions, the type of output is determined from the input:
a number as input will return a number as output, a BigNumber as input
returns a BigNumber as output. But for example the functions
`math.evaluate('2+3')`, `math.parse('2+3')`, `math.range('1:10')`,
and `math.unit('5cm')` use the `number` configuration setting.

Note that `math.sqrt(4)` will always return the number `2` regardless of
the `number` configuration, because the numeric type can be determined from
the input value.

Available values are: `'number'` (default), `'BigNumber'`, `'bigint'`, or `'Fraction'`.
[BigNumbers](../datatypes/bignumbers.js) have higher precision than the default numbers of JavaScript,
[bigint](../datatypes/bigints.md) can represent large integer numbers,
and [`Fractions`](../datatypes/fractions.js) store values in terms of a numerator and
denominator.

- `numberFallback`. When `number` is configured for example with value `'bigint'`,
and a value cannot be represented as `bigint` like in `math.evaluate('2.3')`,
the value will be parsed in the type configured with `numberFallback`.
Available values: `'number'` (default) or `'BigNumber'`.

- `precision`. The maximum number of significant digits for BigNumbers.
This setting only applies to BigNumbers, not to numbers.
Expand Down
54 changes: 54 additions & 0 deletions docs/datatypes/bigints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# BigInts

For calculations with large integer numbers, math.js supports the built-in `bigint` data type.

## Usage

A bigint can be created either by adding the suffix `n` to a `number`, using the `BigInt` constructor function, or using the util function `math.bigint`:

```js
42n
BigInt('42')
math.bigint('42')
```

Most functions can determine the type of output from the type of input:
a `number` as input will return a `number` as output, a `bigint` as input returns
a `bigint` as output. Functions which cannot determine the type of output
from the input (for example `math.evaluate`) use the default number type `number`,
which can be configured when instantiating math.js. To configure the use of
`bigint` instead of [numbers](numbers.md) by default, configure math.js like:

```js
math.config({
number: 'bigint'
})

// use math
math.evaluate('70000000000000000123') // bigint 70000000000000000123n
```

## Support

All basic arithmetic functions in math.js support `bigint`. Since `bigint` can only hold integer values, it is not applicable to for example trigonometric functions. When using a `bigint` in a function that does not support it, like `sqrt`, it will convert the `bigint` into a regular `number` and then execute the function:

```js
math.sin(2n) // number 0.9092974268256817
```

## Conversion

There are utility functions to convert a `bigint` into a `number` or `BigNumber`:

```js
// convert a number to bigint or BigNumber
math.bigint(42) // bigint, 42n
math.bignumber(42) // BigNumber, 42

// convert a bigint to a number or BigNumber
math.number(42n) // number, 42
math.bignumber(42n) // BigNumber, 42

// losing digits when converting to number
math.number(70000000000000000123n) // number, 7000000000000000000
```
3 changes: 3 additions & 0 deletions docs/datatypes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ math.sqrt(4.41e2) // 21
// use BigNumbers
math.add(math.bignumber(0.1), math.bignumber(0.2)) // BigNumber, 0.3

// use bigint
math.add(300000000000000000n, 1n) // 300000000000000001n

// use Fractions
math.add(math.fraction(1), math.fraction(3)) // Fraction, 0.(3)

Expand Down
2 changes: 1 addition & 1 deletion docs/datatypes/numbers.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ans = math.add(0.1, 0.2) // 0.30000000000000004
math.format(ans, {precision: 14}) // '0.3'
```

Alternatives are to use [Fractions](fractions.md) which store a number as a numerator and denominator, or [BigNumbers](bignumbers.md), which store a number with a higher precision.
Alternatives are to use [Fractions](fractions.md) which store a number as a numerator and denominator, [BigNumbers](bignumbers.md) which store a number with a higher precision, or [bigint](bigints.md) which can store larger integer numbers.


## Minimum and maximum
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Math.js can be used in the browser, in node.js and in any JavaScript engine. Ins
- **[Data Types](datatypes/index.md)**
- [Numbers](datatypes/numbers.md)
- [BigNumbers](datatypes/bignumbers.md)
- [bigints](datatypes/bigints.md)
- [Fractions](datatypes/fractions.md)
- [Complex Numbers](datatypes/complex_numbers.md)
- [Matrices](datatypes/matrices.md)
Expand Down
42 changes: 0 additions & 42 deletions examples/advanced/use_bigint.js

This file was deleted.

6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@
"engines": {
"node": ">= 18"
},
"browserslist": [
"last 1 version",
"> 0.1%",
"not dead"
],
"bugs": {
"url": "https://github.com/josdejong/mathjs/issues"
},
Expand Down
6 changes: 5 additions & 1 deletion src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ export const DEFAULT_CONFIG = {
// type of default matrix output. Choose 'matrix' (default) or 'array'
matrix: 'Matrix',

// type of default number output. Choose 'number' (default) 'BigNumber', or 'Fraction
// type of default number output. Choose 'number' (default) 'BigNumber', 'bigint', or 'Fraction'
number: 'number',

// type of fallback used for config { number: 'bigint' } when a value cannot be represented
// in the configured numeric type. Choose 'number' (default) or 'BigNumber'.
numberFallback: 'number',

// number of significant digits in BigNumbers
precision: 64,

Expand Down
4 changes: 3 additions & 1 deletion src/core/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
isString,
isSymbolNode,
isUndefined,
isUnit
isUnit,
isBigInt
} from '../utils/is.js'
import { ArgumentsError } from '../error/ArgumentsError.js'
import { DimensionError } from '../error/DimensionError.js'
Expand Down Expand Up @@ -107,6 +108,7 @@ export function create (factories, config) {
isNumber,
isComplex,
isBigNumber,
isBigInt,
isFraction,
isUnit,
isString,
Expand Down
2 changes: 1 addition & 1 deletion src/core/function/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function configFactory (config, emit) {
* {string} matrix
* A string 'Matrix' (default) or 'Array'.
* {string} number
* A string 'number' (default), 'BigNumber', or 'Fraction'
* A string 'number' (default), 'BigNumber', 'bigint', or 'Fraction'
* {number} precision
* The number of significant digits for BigNumbers.
* Not applicable for Numbers.
Expand Down
50 changes: 49 additions & 1 deletion src/core/function/typed.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import {
isString,
isSymbolNode,
isUndefined,
isUnit
isUnit, isBigInt
} from '../../utils/is.js'
import typedFunction from 'typed-function'
import { digits } from '../../utils/number.js'
Expand Down Expand Up @@ -116,6 +116,7 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi
{ name: 'number', test: isNumber },
{ name: 'Complex', test: isComplex },
{ name: 'BigNumber', test: isBigNumber },
{ name: 'bigint', test: isBigInt },
{ name: 'Fraction', test: isFraction },
{ name: 'Unit', test: isUnit },
// The following type matches a valid variable name, i.e., an alphanumeric
Expand Down Expand Up @@ -201,6 +202,37 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi

return new Complex(x.toNumber(), 0)
}
}, {
from: 'bigint',
to: 'number',
convert: function (x) {
if (x > Number.MAX_SAFE_INTEGER) {
throw new TypeError('Cannot implicitly convert bigint to number: ' +
'value exceeds the max safe integer value (value: ' + x + ')')
}

return Number(x)
}
}, {
from: 'bigint',
to: 'BigNumber',
convert: function (x) {
if (!BigNumber) {
throwNoBignumber(x)
}

return new BigNumber(x.toString())
}
}, {
from: 'bigint',
to: 'Fraction',
convert: function (x) {
if (!Fraction) {
throwNoFraction(x)
}

return new Fraction(x.toString())
}
}, {
from: 'Fraction',
to: 'BigNumber',
Expand Down Expand Up @@ -265,6 +297,16 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi
throw new Error('Cannot convert "' + x + '" to BigNumber')
}
}
}, {
from: 'string',
to: 'bigint',
convert: function (x) {
try {
return BigInt(x)
} catch (err) {
throw new Error('Cannot convert "' + x + '" to BigInt')
}
}
}, {
from: 'string',
to: 'Fraction',
Expand Down Expand Up @@ -309,6 +351,12 @@ export const createTyped = /* #__PURE__ */ factory('typed', dependencies, functi

return new BigNumber(+x)
}
}, {
from: 'boolean',
to: 'bigint',
convert: function (x) {
return BigInt(+x)
}
}, {
from: 'boolean',
to: 'Fraction',
Expand Down
1 change: 1 addition & 0 deletions src/entry/typeChecks.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
isArrayNode,
isAssignmentNode,
isBigNumber,
isBigInt,
isBlockNode,
isBoolean,
isChain,
Expand Down
17 changes: 17 additions & 0 deletions src/expression/embeddedDocs/construction/bigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const bigintDocs = {
name: 'bigint',
category: 'Construction',
syntax: [
'bigint(x)'
],
description:
'Create a bigint, an integer with an arbitrary number of digits, from a number or string.',
examples: [
'123123123123123123 # a large number will lose digits',
'bigint("123123123123123123")',
'bignumber(["1", "3", "5"])'
],
seealso: [
'boolean', 'bignumber', 'number', 'complex', 'fraction', 'index', 'matrix', 'string', 'unit'
]
}
2 changes: 1 addition & 1 deletion src/expression/embeddedDocs/construction/bignumber.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ export const bignumberDocs = {
'bignumber([0.1, 0.2, 0.3])'
],
seealso: [
'boolean', 'complex', 'fraction', 'index', 'matrix', 'string', 'unit'
'boolean', 'bigint', 'complex', 'fraction', 'index', 'matrix', 'string', 'unit'
]
}
2 changes: 1 addition & 1 deletion src/expression/embeddedDocs/construction/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ export const numberDocs = {
'number(unit("52cm"), "m")'
],
seealso: [
'bignumber', 'boolean', 'complex', 'fraction', 'index', 'matrix', 'string', 'unit'
'bignumber', 'bigint', 'boolean', 'complex', 'fraction', 'index', 'matrix', 'string', 'unit'
]
}
Loading

0 comments on commit ab3461d

Please sign in to comment.