Add docs and more functions

This commit is contained in:
Mikko Ahlroth 2024-02-04 16:36:17 +02:00
parent 868bee8a06
commit b4c4608577
6 changed files with 281 additions and 22 deletions

View file

@ -3,14 +3,44 @@
[![Package Version](https://img.shields.io/hexpm/v/bigi)](https://hex.pm/packages/bigi) [![Package Version](https://img.shields.io/hexpm/v/bigi)](https://hex.pm/packages/bigi)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/bigi/) [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/bigi/)
Arbitrary precision integer arithmetic for Gleam.
In the Erlang target, all integers are automatically "big integers" that are subject to
arbitrary precision arithmetic. This means there is no need for this package if you are only
targeting Erlang.
In JavaScript, Gleam's `Int` type corresponds to the `number` type, which is implemented using
floating-point arithmetic. That means it's subject to the following limits:
- [Maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)
and
[minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER)
beyond which the accuracy of arithmetic suffers, and
- [maximum value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_VALUE)
and
[minimum value](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_VALUE)
beyond which the numbers are converted to `Infinity` or `-Infinity`.
This package thus provides big integers for the JavaScript target and additionally provides a
consistent interface for packages that target both Erlang and JavaScript. In Erlang, regular
integers are used. In JavaScript, the
[`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
type is used.
```sh ```sh
gleam add bigi gleam add bigi
``` ```
```gleam ```gleam
import bigi import bigi
pub fn main() { pub fn main() {
// TODO: An example of the project in use bigi.power(
bigi.from_int(2),
bigi.from_int(65_535)
)
// 1001764965203423232489536175780127875223912737784875709632508486855447029778...
} }
``` ```

View file

@ -12,6 +12,8 @@ version = "1.0.0"
# For a full reference of all the available options, you can have a look at # For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/. # https://gleam.run/writing-gleam/gleam-toml/.
[dev-dependencies] [dependencies]
gleam_stdlib = "~> 0.34 or ~> 1.0" gleam_stdlib = "~> 0.34 or ~> 1.0"
[dev-dependencies]
gleeunit = "~> 1.0" gleeunit = "~> 1.0"

View file

@ -1,45 +1,138 @@
import gleam/order
/// A big integer.
pub type BigInt pub type BigInt
@external(erlang, "bigi_ffi", "from") /// Create a big integer representing zero.
@external(javascript, "./bigi_ffi.mjs", "from")
pub fn from_int(int: Int) -> BigInt
@external(erlang, "bigi_ffi", "to")
@external(javascript, "./bigi_ffi.mjs", "to")
pub fn to_int(bigint: BigInt) -> Result(Int, Nil)
@external(erlang, "bigi_ffi", "to_string")
@external(javascript, "./bigi_ffi.mjs", "to_string")
pub fn to_string(bigint: BigInt) -> String
@external(erlang, "bigi_ffi", "zero") @external(erlang, "bigi_ffi", "zero")
@external(javascript, "./bigi_ffi.mjs", "zero") @external(javascript, "./bigi_ffi.mjs", "zero")
pub fn zero() -> BigInt pub fn zero() -> BigInt
/// Create a big integer from a regular integer.
@external(erlang, "bigi_ffi", "from")
@external(javascript, "./bigi_ffi.mjs", "from")
pub fn from_int(int: Int) -> BigInt
/// Convert the big integer to a regular integer.
///
/// In Erlang, this cannot fail, as all Erlang integers are big integers. In the
/// JavaScript target, this will fail if the integer is bigger than the
/// [maximum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER)
/// or smaller than the
/// [minimum safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER).
@external(erlang, "bigi_ffi", "to")
@external(javascript, "./bigi_ffi.mjs", "to")
pub fn to_int(bigint: BigInt) -> Result(Int, Nil)
/// Convert the big integer into a simple string - a sequence of digits.
@external(erlang, "erlang", "integer_to_binary")
@external(javascript, "./bigi_ffi.mjs", "to_string")
pub fn to_string(bigint: BigInt) -> String
/// Compare two big integers, returning an order that denotes if `b` is lower,
/// bigger than, or equal to `a`.
@external(erlang, "bigi_ffi", "compare")
@external(javascript, "./bigi_ffi.mjs", "compare")
pub fn compare(a: BigInt, with b: BigInt) -> order.Order
/// Get the absolute value of a big integer.
@external(erlang, "erlang", "abs")
@external(javascript, "./bigi_ffi.mjs", "absolute")
pub fn absolute(bigint: BigInt) -> BigInt
/// Add two big integers together.
@external(erlang, "bigi_ffi", "add") @external(erlang, "bigi_ffi", "add")
@external(javascript, "./bigi_ffi.mjs", "add") @external(javascript, "./bigi_ffi.mjs", "add")
pub fn add(a: BigInt, b: BigInt) -> BigInt pub fn add(a: BigInt, b: BigInt) -> BigInt
/// Subtract the subtrahend from the minuend.
@external(erlang, "bigi_ffi", "subtract") @external(erlang, "bigi_ffi", "subtract")
@external(javascript, "./bigi_ffi.mjs", "subtract") @external(javascript, "./bigi_ffi.mjs", "subtract")
pub fn subtract(a: BigInt, b: BigInt) -> BigInt pub fn subtract(minuend a: BigInt, subtrahend b: BigInt) -> BigInt
/// Multiply two big integers together.
@external(erlang, "bigi_ffi", "multiply") @external(erlang, "bigi_ffi", "multiply")
@external(javascript, "./bigi_ffi.mjs", "multiply") @external(javascript, "./bigi_ffi.mjs", "multiply")
pub fn multiply(a: BigInt, b: BigInt) -> BigInt pub fn multiply(a: BigInt, b: BigInt) -> BigInt
/// Divide the dividend with the divisor using integer division.
///
/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.
@external(erlang, "bigi_ffi", "divide") @external(erlang, "bigi_ffi", "divide")
@external(javascript, "./bigi_ffi.mjs", "divide") @external(javascript, "./bigi_ffi.mjs", "divide")
pub fn divide(a: BigInt, b: BigInt) -> BigInt pub fn divide(dividend a: BigInt, divisor b: BigInt) -> BigInt
/// Divide the dividend with the divisor using integer division.
///
/// Returns an error if the divisor is 0.
@external(erlang, "bigi_ffi", "divide_no_zero")
@external(javascript, "./bigi_ffi.mjs", "divide_no_zero")
pub fn divide_no_zero(
dividend a: BigInt,
divisor b: BigInt,
) -> Result(BigInt, Nil)
/// Divide the dividend with the divisor using integer division and return the
/// remainder.
///
/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.
@external(erlang, "bigi_ffi", "remainder") @external(erlang, "bigi_ffi", "remainder")
@external(javascript, "./bigi_ffi.mjs", "remainder") @external(javascript, "./bigi_ffi.mjs", "remainder")
pub fn remainder(a: BigInt, b: BigInt) -> BigInt pub fn remainder(dividend a: BigInt, divisor b: BigInt) -> BigInt
/// Divide the dividend with the divisor using integer division and return the
/// remainder.
///
/// Returns an error if the divisor is 0.
@external(erlang, "bigi_ffi", "remainder_no_zero")
@external(javascript, "./bigi_ffi.mjs", "remainder_no_zero")
pub fn remainder_no_zero(
dividend a: BigInt,
divisor b: BigInt,
) -> Result(BigInt, Nil)
/// Calculate a mathematical modulo operation.
///
/// Follows the standard Gleam divide-by-zero rule of 0 when the divisor is 0.
@external(erlang, "bigi_ffi", "modulo") @external(erlang, "bigi_ffi", "modulo")
@external(javascript, "./bigi_ffi.mjs", "modulo") @external(javascript, "./bigi_ffi.mjs", "modulo")
pub fn modulo(a: BigInt, b: BigInt) -> BigInt pub fn modulo(dividend a: BigInt, divisor b: BigInt) -> BigInt
/// Calculate a mathematical modulo operation.
///
/// Returns an error if the divisor is 0.
@external(erlang, "bigi_ffi", "modulo_no_zero")
@external(javascript, "./bigi_ffi.mjs", "modulo_no_zero")
pub fn modulo_no_zero(
dividend a: BigInt,
divisor b: BigInt,
) -> Result(BigInt, Nil)
/// Raise the base to the exponent.
@external(erlang, "bigi_ffi", "power") @external(erlang, "bigi_ffi", "power")
@external(javascript, "./bigi_ffi.mjs", "power") @external(javascript, "./bigi_ffi.mjs", "power")
pub fn power(a: BigInt, b: BigInt) -> BigInt pub fn power(base a: BigInt, exponent b: BigInt) -> BigInt
/// Get the digits in a given bigint as a list of integers.
///
/// The list is ordered starting from the most significant digit.
pub fn digits(bigint: BigInt) {
let divisor = from_int(10)
get_digit(bigint, [], divisor)
}
fn get_digit(bigint: BigInt, digits: List(Int), divisor: BigInt) {
case compare(bigint, divisor) {
order.Gt -> {
let assert Ok(digit) = to_int(bigint)
[digit, ..digits]
}
_ -> {
let assert Ok(digit) =
remainder(bigint, divisor)
|> to_int()
let digits = [digit, ..digits]
get_digit(divide(bigint, divisor), digits, divisor)
}
}
}

View file

@ -3,14 +3,17 @@
-export([ -export([
from/1, from/1,
to/1, to/1,
to_string/1,
zero/0, zero/0,
compare/2,
add/2, add/2,
subtract/2, subtract/2,
multiply/2, multiply/2,
divide/2, divide/2,
divide_no_zero/2,
remainder/2, remainder/2,
remainder_no_zero/2,
modulo/2, modulo/2,
modulo_no_zero/2,
power/2 power/2
]). ]).
@ -18,22 +21,36 @@ from(Int) -> Int.
to(BigInt) -> {ok, BigInt}. to(BigInt) -> {ok, BigInt}.
to_string(BigInt) -> erlang:integer_to_binary(BigInt).
zero() -> 0. zero() -> 0.
compare(A, B) when A < B -> gt;
compare(A, B) when A > B -> lt;
compare(_, _) -> eq.
add(A, B) -> A + B. add(A, B) -> A + B.
subtract(A, B) -> A - B. subtract(A, B) -> A - B.
multiply(A, B) -> A * B. multiply(A, B) -> A * B.
divide(_, 0) -> 0;
divide(A, B) -> A div B. divide(A, B) -> A div B.
divide_no_zero(_, 0) -> {error, nil};
divide_no_zero(A, B) -> {ok, divide(A, B)}.
remainder(_, 0) -> 0;
remainder(A, B) -> A rem B. remainder(A, B) -> A rem B.
remainder_no_zero(_, 0) -> {error, nil};
remainder_no_zero(A, B) -> {ok, remainder(A, B)}.
modulo(_, 0) -> 0;
modulo(A, B) -> ((A rem B) + B) rem B. modulo(A, B) -> ((A rem B) + B) rem B.
modulo_no_zero(_, 0) -> {error, nil};
modulo_no_zero(A, B) -> {ok, modulo(A, B)}.
power(_, 0) -> power(_, 0) ->
1; 1;
power(A, 1) -> power(A, 1) ->

View file

@ -1,4 +1,5 @@
import { Ok, Error } from "./gleam.mjs"; import { Ok, Error } from "./gleam.mjs";
import { Lt, Eq, Gt } from "../gleam_stdlib/gleam/order.mjs";
export function from(int) { export function from(int) {
return BigInt(int); return BigInt(int);
@ -20,6 +21,24 @@ export function zero() {
return 0n; return 0n;
} }
export function compare(a, b) {
if (a < b) {
return new Gt();
} else if (a > b) {
return new Lt();
} else {
return new Eq();
}
}
export function absolute(bigint) {
if (bigint < 0) {
return -bigint;
} else {
return bigint;
}
}
export function add(a, b) { export function add(a, b) {
return a + b; return a + b;
} }
@ -33,17 +52,53 @@ export function multiply(a, b) {
} }
export function divide(a, b) { export function divide(a, b) {
if (b === 0n) {
return 0n;
}
return a / b; return a / b;
} }
export function divide_no_zero(a, b) {
if (b === 0n) {
return new Error(undefined);
}
return new Ok(divide(a, b));
}
export function remainder(a, b) { export function remainder(a, b) {
if (b === 0n) {
return 0n;
}
return a % b; return a % b;
} }
export function remainder_no_zero(a, b) {
if (b === 0n) {
return new Error(undefined);
}
return new Ok(remainder(a, b));
}
export function modulo(a, b) { export function modulo(a, b) {
if (b === 0n) {
return 0n;
}
return ((a % b) + b) % b; return ((a % b) + b) % b;
} }
export function modulo_no_zero(a, b) {
if (b === 0n) {
return new Error(undefined);
}
return new Ok(modulo(a, b));
}
export function power(a, b) { export function power(a, b) {
return a ** b; return a ** b;
} }

View file

@ -1,3 +1,4 @@
import gleam/order
import gleeunit import gleeunit
import gleeunit/should import gleeunit/should
import bigi import bigi
@ -19,6 +20,17 @@ pub fn zero_test() {
) )
} }
pub fn compare_test() {
should.equal(bigi.compare(bigi.from_int(1), bigi.from_int(2)), order.Gt)
should.equal(bigi.compare(bigi.from_int(2), bigi.from_int(1)), order.Lt)
should.equal(bigi.compare(bigi.from_int(1), bigi.from_int(1)), order.Eq)
}
pub fn absolute_test() {
should.equal(bigi.absolute(bigi.from_int(1)), bigi.from_int(1))
should.equal(bigi.absolute(bigi.from_int(-1234)), bigi.from_int(1234))
}
pub fn add_test() { pub fn add_test() {
should.equal( should.equal(
bigi.add(bigi.from_int(js_max_safe_int), bigi.from_int(2)) bigi.add(bigi.from_int(js_max_safe_int), bigi.from_int(2))
@ -59,6 +71,21 @@ pub fn divide_test() {
) )
} }
pub fn divide_zero_test() {
should.equal(bigi.divide(bigi.from_int(1), bigi.zero()), bigi.zero())
}
pub fn divide_no_zero_test() {
should.equal(
bigi.divide_no_zero(bigi.from_int(1), bigi.from_int(1)),
Ok(bigi.from_int(1)),
)
}
pub fn divide_no_zero_error_test() {
should.be_error(bigi.divide_no_zero(bigi.from_int(1), bigi.zero()))
}
pub fn remainder_test() { pub fn remainder_test() {
should.equal( should.equal(
bigi.remainder(bigi.from_int(-5), bigi.from_int(3)), bigi.remainder(bigi.from_int(-5), bigi.from_int(3)),
@ -66,6 +93,21 @@ pub fn remainder_test() {
) )
} }
pub fn remainder_zero_test() {
should.equal(bigi.remainder(bigi.from_int(1), bigi.zero()), bigi.zero())
}
pub fn remainder_no_zero_test() {
should.equal(
bigi.remainder_no_zero(bigi.from_int(1), bigi.from_int(1)),
Ok(bigi.from_int(0)),
)
}
pub fn remainder_no_zero_error_test() {
should.be_error(bigi.remainder_no_zero(bigi.from_int(1), bigi.zero()))
}
pub fn modulo_test() { pub fn modulo_test() {
should.equal( should.equal(
bigi.modulo(bigi.from_int(-5), bigi.from_int(3)), bigi.modulo(bigi.from_int(-5), bigi.from_int(3)),
@ -73,6 +115,26 @@ pub fn modulo_test() {
) )
} }
pub fn modulo_zero_test() {
should.equal(bigi.modulo(bigi.from_int(1), bigi.zero()), bigi.zero())
}
pub fn modulo_no_zero_test() {
should.equal(
bigi.modulo_no_zero(bigi.from_int(1), bigi.from_int(1)),
Ok(bigi.from_int(0)),
)
}
pub fn modulo_no_zero_error_test() {
should.be_error(bigi.modulo_no_zero(bigi.from_int(1), bigi.zero()))
}
pub fn digits_test() {
let bigint = bigi.power(bigi.from_int(2), bigi.from_int(16))
should.equal(bigi.digits(bigint), [6, 5, 5, 3, 6])
}
pub fn power_test() { pub fn power_test() {
should.equal( should.equal(
bigi.to_string(bigi.power(bigi.from_int(2), bigi.from_int(65_535))), bigi.to_string(bigi.power(bigi.from_int(2), bigi.from_int(65_535))),