Add docs and more functions
This commit is contained in:
parent
868bee8a06
commit
b4c4608577
6 changed files with 281 additions and 22 deletions
32
README.md
32
README.md
|
@ -3,14 +3,44 @@
|
|||
[![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/)
|
||||
|
||||
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
|
||||
gleam add bigi
|
||||
```
|
||||
|
||||
```gleam
|
||||
import bigi
|
||||
|
||||
pub fn main() {
|
||||
// TODO: An example of the project in use
|
||||
bigi.power(
|
||||
bigi.from_int(2),
|
||||
bigi.from_int(65_535)
|
||||
)
|
||||
|
||||
// 1001764965203423232489536175780127875223912737784875709632508486855447029778...
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ version = "1.0.0"
|
|||
# For a full reference of all the available options, you can have a look at
|
||||
# https://gleam.run/writing-gleam/gleam-toml/.
|
||||
|
||||
[dev-dependencies]
|
||||
[dependencies]
|
||||
gleam_stdlib = "~> 0.34 or ~> 1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = "~> 1.0"
|
||||
|
|
127
src/bigi.gleam
127
src/bigi.gleam
|
@ -1,45 +1,138 @@
|
|||
import gleam/order
|
||||
|
||||
/// A big integer.
|
||||
pub type BigInt
|
||||
|
||||
@external(erlang, "bigi_ffi", "from")
|
||||
@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
|
||||
|
||||
/// Create a big integer representing zero.
|
||||
@external(erlang, "bigi_ffi", "zero")
|
||||
@external(javascript, "./bigi_ffi.mjs", "zero")
|
||||
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(javascript, "./bigi_ffi.mjs", "add")
|
||||
pub fn add(a: BigInt, b: BigInt) -> BigInt
|
||||
|
||||
/// Subtract the subtrahend from the minuend.
|
||||
@external(erlang, "bigi_ffi", "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(javascript, "./bigi_ffi.mjs", "multiply")
|
||||
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(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(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(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(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,17 @@
|
|||
-export([
|
||||
from/1,
|
||||
to/1,
|
||||
to_string/1,
|
||||
zero/0,
|
||||
compare/2,
|
||||
add/2,
|
||||
subtract/2,
|
||||
multiply/2,
|
||||
divide/2,
|
||||
divide_no_zero/2,
|
||||
remainder/2,
|
||||
remainder_no_zero/2,
|
||||
modulo/2,
|
||||
modulo_no_zero/2,
|
||||
power/2
|
||||
]).
|
||||
|
||||
|
@ -18,22 +21,36 @@ from(Int) -> Int.
|
|||
|
||||
to(BigInt) -> {ok, BigInt}.
|
||||
|
||||
to_string(BigInt) -> erlang:integer_to_binary(BigInt).
|
||||
|
||||
zero() -> 0.
|
||||
|
||||
compare(A, B) when A < B -> gt;
|
||||
compare(A, B) when A > B -> lt;
|
||||
compare(_, _) -> eq.
|
||||
|
||||
add(A, B) -> A + B.
|
||||
|
||||
subtract(A, B) -> A - B.
|
||||
|
||||
multiply(A, B) -> A * B.
|
||||
|
||||
divide(_, 0) -> 0;
|
||||
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_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_no_zero(_, 0) -> {error, nil};
|
||||
modulo_no_zero(A, B) -> {ok, modulo(A, B)}.
|
||||
|
||||
power(_, 0) ->
|
||||
1;
|
||||
power(A, 1) ->
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Ok, Error } from "./gleam.mjs";
|
||||
import { Lt, Eq, Gt } from "../gleam_stdlib/gleam/order.mjs";
|
||||
|
||||
export function from(int) {
|
||||
return BigInt(int);
|
||||
|
@ -20,6 +21,24 @@ export function zero() {
|
|||
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) {
|
||||
return a + b;
|
||||
}
|
||||
|
@ -33,17 +52,53 @@ export function multiply(a, b) {
|
|||
}
|
||||
|
||||
export function divide(a, b) {
|
||||
if (b === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (b === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (b === 0n) {
|
||||
return 0n;
|
||||
}
|
||||
|
||||
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) {
|
||||
return a ** b;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import gleam/order
|
||||
import gleeunit
|
||||
import gleeunit/should
|
||||
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() {
|
||||
should.equal(
|
||||
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() {
|
||||
should.equal(
|
||||
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() {
|
||||
should.equal(
|
||||
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() {
|
||||
should.equal(
|
||||
bigi.to_string(bigi.power(bigi.from_int(2), bigi.from_int(65_535))),
|
||||
|
|
Loading…
Reference in a new issue