Add docs, change second parameter of math operations to bigint
This commit is contained in:
parent
3a6031482d
commit
ebb333586e
25 changed files with 568 additions and 147 deletions
235
README.md
235
README.md
|
@ -3,23 +3,232 @@
|
|||
[![Package Version](https://img.shields.io/hexpm/v/ranged_int)](https://hex.pm/packages/ranged_int)
|
||||
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/ranged_int/)
|
||||
|
||||
```sh
|
||||
gleam add ranged_int
|
||||
```
|
||||
```gleam
|
||||
import ranged_int
|
||||
Type safe ranged integer operations for Gleam.
|
||||
|
||||
pub fn main() {
|
||||
// TODO: An example of the project in use
|
||||
Some features:
|
||||
|
||||
- Builtin types for the most used ranges
|
||||
- Create custom types for your own ranges in a type safe way
|
||||
- Big integer arithmetic to avoid loss of precision
|
||||
- Opt-in support for overflow and underflow for all ranges (with both a minimum and maximum limit)
|
||||
- Partially limited ranges (only a minimum or maximum limit)
|
||||
- Generic ranged integers for cases where ranges are not known at compile time
|
||||
|
||||
This library uses the [bigi](https://hex.pm/packages/bigi) library for handling big
|
||||
integers. The API operates on big integers and only a few builtin types have helpers for
|
||||
working with regular Gleam `Int`s. This is to ensure correctness on both targets by
|
||||
avoiding the JavaScript
|
||||
[number type limitations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#integer_range_for_number).
|
||||
|
||||
When dealing with math operations, it is useful to check the
|
||||
[bigi documentation](https://hexdocs.pm/bigi), as the math operations merely delegate to
|
||||
the bigi math functions.
|
||||
|
||||
## Overflow
|
||||
|
||||
Whenever a ranged integer is created from an unranged big integer, or a math operation
|
||||
is performed on a ranged integer, the result is either a new ranged integer, or
|
||||
overflow. In this context overflow includes both overflow and underflow, i.e. the
|
||||
ranged integer's maximum or minimum limit being broken, respectively.
|
||||
|
||||
If wanted, the resulting `utils.Overflow` type can then be passed on to the
|
||||
`interface.overflow` or `interface.eject` functions to decide how to handle the
|
||||
situation: the former will calculate a new value with the overflow algorithm, and the
|
||||
latter will return an unranged big integer. An example with `ranged_int/builtin/uint8`,
|
||||
where these functions are wrapped:
|
||||
|
||||
```gleam
|
||||
let assert Ok(a) = uint8.from_bigint(bigi.from_int(120))
|
||||
let b = bigi.from_int(160)
|
||||
uint8.overflow(uint8.add(a, b)) // Uint8(24)
|
||||
uint8.eject(uint8.add(a, b)) // BigInt(280)
|
||||
```
|
||||
|
||||
### Overflow algorithm
|
||||
|
||||
The overflow algorithm is based on how unsigned integer overflow is done in the C
|
||||
language. In C it is not defined for signed integers, but this library defines it for all
|
||||
ranged integers. In a nutshell:
|
||||
|
||||
1. Shift the range into a positive range, starting from 0.
|
||||
1. Shift the value to overflow the same amount.
|
||||
1. Do unsigned integer overflow or underflow.
|
||||
1. Shift the answer back to the original range.
|
||||
|
||||
## The builtin types
|
||||
|
||||
In the `ranged_int/builtin` namespace there are builtin types for the most popular
|
||||
ranges: unsigned and signed integers from 8 to 128 bits, and an unsigned integer of
|
||||
unlimited size. When implementing your own integer range, you may wish to consult the
|
||||
sources of these implementations as a template.
|
||||
|
||||
## Implementing your own type
|
||||
|
||||
If none of the builtin types match your use case, you can create your own type with
|
||||
custom limits. Creating your own type allows for type safety, for example preventing
|
||||
accidentally passing a regular `Int` in place of your ranged integer, or mixing different
|
||||
kinds of ranged integers in the same `List`.
|
||||
|
||||
Creating your own type requires that the possible limits are known at compile time. If
|
||||
you don't know the limits until runtime, you should take a look at the builtin generic
|
||||
ranged integer.
|
||||
|
||||
### Minimum viable product
|
||||
|
||||
Let's say you want an integer that tracks the years the band
|
||||
[Katzenjammer](<https://en.wikipedia.org/wiki/Katzenjammer_(band)>) was active. It's
|
||||
understandable, those were some good years. Let's make a minimal ranged integer that
|
||||
goes from 2007 to 2015:
|
||||
|
||||
```gleam
|
||||
import bigi.{type BigInt}
|
||||
import ranged_int/interface.{type Interface, Interface}
|
||||
|
||||
const max_limit = 2015
|
||||
|
||||
const min_limit = 2007
|
||||
|
||||
pub opaque type Katzen {
|
||||
Katzen(data: BigInt)
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/ranged_int>.
|
||||
This is just a start, and you can't do anything with this yet. But we define the limits
|
||||
and an opaque type to represent our data. The type is opaque to prevent anyone from
|
||||
outside the module touching it, because that would ruin our correctness guarantees.
|
||||
|
||||
## Development
|
||||
Next, let's define the minimal set of functions to operate with the type:
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
gleam shell # Run an Erlang shell
|
||||
```gleam
|
||||
pub fn to_bigint(value: Katzen) {
|
||||
value.data
|
||||
}
|
||||
|
||||
pub fn limits() {
|
||||
interface.overflowable_limits(
|
||||
bigi.from_int(min_limit),
|
||||
bigi.from_int(max_limit),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_bigint_unsafe(value: BigInt) {
|
||||
Katzen(data: value)
|
||||
}
|
||||
```
|
||||
|
||||
- `to_bigint` converts the ranged integer to an unlimited big integer.
|
||||
- `limits` sets the limits of the integer. There are helper functions in `interface` for
|
||||
constructing these limits.
|
||||
- `from_bigint_unsafe` constructs the value _unsafely_ from an unlimited big integer.
|
||||
This function is for the library's internal use and not for other use, as it breaks the
|
||||
guarantees of the library.
|
||||
|
||||
Finally, we need an _interface_ structure that tells the library how to use your type:
|
||||
|
||||
```gleam
|
||||
pub const iface: Interface(Katzen, interface.Overflowable) = Interface(
|
||||
from_bigint_unsafe: from_bigint_unsafe,
|
||||
to_bigint: to_bigint,
|
||||
limits: limits,
|
||||
)
|
||||
```
|
||||
|
||||
The interface contains references to the aforementioned functions, that the library
|
||||
needs to operate on your integers. The second type parameter defines if your integer can
|
||||
use the [overflow](#overflow) operation: if `interface.NonOverflowable` was passed, the
|
||||
type system would prevent that. Note that overflowing is still opt-in, and must be
|
||||
explicitly called.
|
||||
|
||||
Note that [due to a Gleam bug](https://github.com/gleam-lang/gleam/issues/2178), the
|
||||
functions used in a public constant have to be public, even though `limits` and
|
||||
`from_bigint_unsafe` would be better suited as private. If you define the constant as
|
||||
private and define wrapper functions for all the `interface` API functions (described
|
||||
later), then these functions may also be private.
|
||||
|
||||
Now this module is ready to be used:
|
||||
|
||||
```gleam
|
||||
import gleam/order
|
||||
import bigi
|
||||
import gleeunit/should
|
||||
import ranged_int/interface
|
||||
import ranged_int/utils
|
||||
import mvp
|
||||
|
||||
pub fn mvp_test() {
|
||||
let assert Ok(a) = interface.from_bigint(bigi.from_int(2007), mvp.iface)
|
||||
let assert Ok(b) = interface.from_bigint(bigi.from_int(2009), mvp.iface)
|
||||
let c = interface.compare(a, b, mvp.iface)
|
||||
should.equal(c, order.Gt)
|
||||
}
|
||||
```
|
||||
|
||||
We can also do math on the integers:
|
||||
|
||||
```gleam
|
||||
pub fn mvp_add_test() {
|
||||
let assert Ok(a) = interface.from_bigint(bigi.from_int(2007), mvp.iface)
|
||||
let b = bigi.from_int(9)
|
||||
let c = interface.math_op(a, b, mvp.iface, bigi.add)
|
||||
should.equal(c, Error(utils.DidOverflow(bigi.from_int(1))))
|
||||
}
|
||||
```
|
||||
|
||||
Here we see that the addition failed because it would have overflowed the allowed range.
|
||||
|
||||
### Prettifying the API
|
||||
|
||||
Now, `interface.math_op(a, b, mvp.iface, bigi.add)` is really a mouthful. It calls the
|
||||
`interface` module's generic math operation function, passing the operands, the ranged
|
||||
integer's interface, and the math operation itself (from `bigi`). It works, but it's not
|
||||
nice to use, and is prone to mistakes (you could replace `bigi.add` with `bigi.subtract`
|
||||
and the type system wouldn't be able to help you).
|
||||
|
||||
To make the type easier to use, we can wrap the functions of the `interface` module:
|
||||
|
||||
```gleam
|
||||
pub fn from_bigint(value: BigInt) {
|
||||
interface.from_bigint(value, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Katzen, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn compare(a: Katzen, b: Katzen) {
|
||||
interface.compare(a, b, iface)
|
||||
}
|
||||
```
|
||||
|
||||
This way, the user can call e.g. `mvp.add(a, b)` directly, without having to mess with
|
||||
the interface and the underlying math functions. The API is now nicer and safer to use!
|
||||
Additionally, the interface constant and the `limits` and `from_bigint_unsafe` can be
|
||||
made private, meaning the only way to use the integer is via the functions you choose to
|
||||
implement.
|
||||
|
||||
We can now see that the integer's usage is simpler:
|
||||
|
||||
```gleam
|
||||
import gleam/order
|
||||
import bigi
|
||||
import gleeunit/should
|
||||
import ranged_int/utils
|
||||
import readme/mvp2
|
||||
|
||||
pub fn mvp2_test() {
|
||||
let assert Ok(a) = mvp2.from_bigint(bigi.from_int(2007))
|
||||
let assert Ok(b) = mvp2.from_bigint(bigi.from_int(2009))
|
||||
let c = mvp2.compare(a, b)
|
||||
should.equal(c, order.Gt)
|
||||
}
|
||||
|
||||
pub fn mvp2_add_test() {
|
||||
let assert Ok(a) = mvp2.from_bigint(bigi.from_int(2007))
|
||||
let b = bigi.from_int(9)
|
||||
let c = mvp2.add(a, b)
|
||||
should.equal(c, Error(utils.DidOverflow(bigi.from_int(1))))
|
||||
}
|
||||
```
|
||||
|
||||
You may wish to check out the sources of the builtin implementations for seeing how they
|
||||
can be done, and for easier copying of the boilerplate.
|
||||
|
|
|
@ -4,9 +4,9 @@ version = "1.0.0"
|
|||
# Fill out these fields if you intend to generate HTML documentation or publish
|
||||
# your project to the Hex package manager.
|
||||
#
|
||||
# description = ""
|
||||
# licences = ["Apache-2.0"]
|
||||
# repository = { type = "github", user = "username", repo = "project" }
|
||||
description = "Type safe ranged integer operations for Gleam."
|
||||
licences = ["MIT"]
|
||||
repository = { type = "gitlab", user = "Nicd", repo = "ranged_int" }
|
||||
# links = [{ title = "Website", href = "https://gleam.run" }]
|
||||
#
|
||||
# For a full reference of all the available options, you can have a look at
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,12 +1,24 @@
|
|||
//// A generic ranged integer.
|
||||
////
|
||||
//// This module is meant for cases where the integer range cannot be known at
|
||||
//// compile time. Generic ranged integers are less type safe and have lower
|
||||
//// performance. It's always suggested to use one of the builtin types or to
|
||||
//// create your own type when you can.
|
||||
////
|
||||
//// Any two-operand math operations will use the range of the first operand as
|
||||
//// the range of the output value.
|
||||
|
||||
import bigi.{type BigInt}
|
||||
import ranged_int/interface.{type Interface, type Overflowable, Interface}
|
||||
|
||||
/// The interface of a generic ranged integer.
|
||||
pub opaque type GenericInterface(overflow_mode) {
|
||||
GenericInterface(
|
||||
interface: Interface(RangedInt(overflow_mode), overflow_mode),
|
||||
)
|
||||
}
|
||||
|
||||
/// A generic ranged integer, carrying its own interface along with the data.
|
||||
pub opaque type RangedInt(overflow_mode) {
|
||||
RangedInt(
|
||||
data: BigInt,
|
||||
|
@ -14,16 +26,20 @@ pub opaque type RangedInt(overflow_mode) {
|
|||
)
|
||||
}
|
||||
|
||||
/// Create from a big integer and a minimum and maximum value, both inclusive.
|
||||
/// The created integer can be used with `overflow`.
|
||||
pub fn from_bigint_overflowable(value: BigInt, min min: BigInt, max max: BigInt) {
|
||||
let iface = gen_overflowable_interface(min, max)
|
||||
interface.from_bigint(value, iface)
|
||||
}
|
||||
|
||||
/// Create from a big integer and a minimum value, inclusive.
|
||||
pub fn from_bigint_min(value: BigInt, min min: BigInt) {
|
||||
let iface = gen_min_interface(min)
|
||||
interface.from_bigint(value, iface)
|
||||
}
|
||||
|
||||
/// Create from a big integer and a maximum value, inclusive.
|
||||
pub fn from_bigint_max(value: BigInt, max max: BigInt) {
|
||||
let iface = gen_max_interface(max)
|
||||
interface.from_bigint(value, iface)
|
||||
|
@ -33,6 +49,8 @@ pub fn to_bigint(int: RangedInt(overflow_mode)) {
|
|||
int.data
|
||||
}
|
||||
|
||||
/// Get the interface of the integer. This interface is required for `overflow`
|
||||
/// and `eject`.
|
||||
pub fn get_interface(int: RangedInt(overflow_mode)) {
|
||||
GenericInterface(int.interface())
|
||||
}
|
||||
|
@ -41,31 +59,31 @@ pub fn compare(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
|||
interface.compare(a, b, a.interface())
|
||||
}
|
||||
|
||||
pub fn add(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn add(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn subtract(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn multiply(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn divide(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.divide)
|
||||
}
|
||||
|
||||
pub fn modulo(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn modulo(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn remainder(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn remainder(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn power(a: RangedInt(overflow_mode), b: RangedInt(overflow_mode)) {
|
||||
pub fn power(a: RangedInt(overflow_mode), b: BigInt) {
|
||||
interface.math_op(a, b, a.interface(), bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,43 +23,43 @@ pub fn compare(a: Int128, b: Int128) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Int128, b: Int128) {
|
||||
pub fn add(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Int128, b: Int128) {
|
||||
pub fn subtract(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Int128, b: Int128) {
|
||||
pub fn multiply(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Int128, b: Int128) {
|
||||
pub fn divide(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Int128, b: Int128) {
|
||||
pub fn divide_no_zero(a: Int128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Int128, b: Int128) {
|
||||
pub fn modulo(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Int128, b: Int128) {
|
||||
pub fn modulo_no_zero(a: Int128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Int128, b: Int128) {
|
||||
pub fn remainder(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Int128, b: Int128) {
|
||||
pub fn remainder_no_zero(a: Int128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Int128, b: Int128) {
|
||||
pub fn power(a: Int128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Int16, b: Int16) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Int16, b: Int16) {
|
||||
pub fn add(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Int16, b: Int16) {
|
||||
pub fn subtract(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Int16, b: Int16) {
|
||||
pub fn multiply(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Int16, b: Int16) {
|
||||
pub fn divide(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Int16, b: Int16) {
|
||||
pub fn divide_no_zero(a: Int16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Int16, b: Int16) {
|
||||
pub fn modulo(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Int16, b: Int16) {
|
||||
pub fn modulo_no_zero(a: Int16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Int16, b: Int16) {
|
||||
pub fn remainder(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Int16, b: Int16) {
|
||||
pub fn remainder_no_zero(a: Int16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Int16, b: Int16) {
|
||||
pub fn power(a: Int16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Int32, b: Int32) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Int32, b: Int32) {
|
||||
pub fn add(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Int32, b: Int32) {
|
||||
pub fn subtract(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Int32, b: Int32) {
|
||||
pub fn multiply(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Int32, b: Int32) {
|
||||
pub fn divide(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Int32, b: Int32) {
|
||||
pub fn divide_no_zero(a: Int32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Int32, b: Int32) {
|
||||
pub fn modulo(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Int32, b: Int32) {
|
||||
pub fn modulo_no_zero(a: Int32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Int32, b: Int32) {
|
||||
pub fn remainder(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Int32, b: Int32) {
|
||||
pub fn remainder_no_zero(a: Int32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Int32, b: Int32) {
|
||||
pub fn power(a: Int32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,43 +23,43 @@ pub fn compare(a: Int64, b: Int64) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Int64, b: Int64) {
|
||||
pub fn add(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Int64, b: Int64) {
|
||||
pub fn subtract(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Int64, b: Int64) {
|
||||
pub fn multiply(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Int64, b: Int64) {
|
||||
pub fn divide(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Int64, b: Int64) {
|
||||
pub fn divide_no_zero(a: Int64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Int64, b: Int64) {
|
||||
pub fn modulo(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Int64, b: Int64) {
|
||||
pub fn modulo_no_zero(a: Int64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Int64, b: Int64) {
|
||||
pub fn remainder(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Int64, b: Int64) {
|
||||
pub fn remainder_no_zero(a: Int64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Int64, b: Int64) {
|
||||
pub fn power(a: Int64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Int8, b: Int8) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Int8, b: Int8) {
|
||||
pub fn add(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Int8, b: Int8) {
|
||||
pub fn subtract(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Int8, b: Int8) {
|
||||
pub fn multiply(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Int8, b: Int8) {
|
||||
pub fn divide(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Int8, b: Int8) {
|
||||
pub fn divide_no_zero(a: Int8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Int8, b: Int8) {
|
||||
pub fn modulo(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Int8, b: Int8) {
|
||||
pub fn modulo_no_zero(a: Int8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Int8, b: Int8) {
|
||||
pub fn remainder(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Int8, b: Int8) {
|
||||
pub fn remainder_no_zero(a: Int8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Int8, b: Int8) {
|
||||
pub fn power(a: Int8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//// An unsigned integer ranging from 0 and upwards.
|
||||
|
||||
import bigi.{type BigInt}
|
||||
import ranged_int/interface.{type Interface, Interface}
|
||||
|
||||
|
@ -25,31 +27,31 @@ pub fn compare(a: Uint, b: Uint) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint, b: Uint) {
|
||||
pub fn add(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint, b: Uint) {
|
||||
pub fn subtract(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint, b: Uint) {
|
||||
pub fn multiply(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint, b: Uint) {
|
||||
pub fn divide(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint, b: Uint) {
|
||||
pub fn modulo(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint, b: Uint) {
|
||||
pub fn remainder(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint, b: Uint) {
|
||||
pub fn power(a: Uint, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,43 +23,43 @@ pub fn compare(a: Uint128, b: Uint128) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint128, b: Uint128) {
|
||||
pub fn add(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint128, b: Uint128) {
|
||||
pub fn subtract(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint128, b: Uint128) {
|
||||
pub fn multiply(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint128, b: Uint128) {
|
||||
pub fn divide(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Uint128, b: Uint128) {
|
||||
pub fn divide_no_zero(a: Uint128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint128, b: Uint128) {
|
||||
pub fn modulo(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Uint128, b: Uint128) {
|
||||
pub fn modulo_no_zero(a: Uint128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint128, b: Uint128) {
|
||||
pub fn remainder(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Uint128, b: Uint128) {
|
||||
pub fn remainder_no_zero(a: Uint128, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint128, b: Uint128) {
|
||||
pub fn power(a: Uint128, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Uint16, b: Uint16) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint16, b: Uint16) {
|
||||
pub fn add(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint16, b: Uint16) {
|
||||
pub fn subtract(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint16, b: Uint16) {
|
||||
pub fn multiply(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint16, b: Uint16) {
|
||||
pub fn divide(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Uint16, b: Uint16) {
|
||||
pub fn divide_no_zero(a: Uint16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint16, b: Uint16) {
|
||||
pub fn modulo(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Uint16, b: Uint16) {
|
||||
pub fn modulo_no_zero(a: Uint16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint16, b: Uint16) {
|
||||
pub fn remainder(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Uint16, b: Uint16) {
|
||||
pub fn remainder_no_zero(a: Uint16, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint16, b: Uint16) {
|
||||
pub fn power(a: Uint16, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Uint32, b: Uint32) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint32, b: Uint32) {
|
||||
pub fn add(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint32, b: Uint32) {
|
||||
pub fn subtract(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint32, b: Uint32) {
|
||||
pub fn multiply(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint32, b: Uint32) {
|
||||
pub fn divide(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Uint32, b: Uint32) {
|
||||
pub fn divide_no_zero(a: Uint32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint32, b: Uint32) {
|
||||
pub fn modulo(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Uint32, b: Uint32) {
|
||||
pub fn modulo_no_zero(a: Uint32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint32, b: Uint32) {
|
||||
pub fn remainder(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Uint32, b: Uint32) {
|
||||
pub fn remainder_no_zero(a: Uint32, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint32, b: Uint32) {
|
||||
pub fn power(a: Uint32, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,43 +23,43 @@ pub fn compare(a: Uint64, b: Uint64) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint64, b: Uint64) {
|
||||
pub fn add(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint64, b: Uint64) {
|
||||
pub fn subtract(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint64, b: Uint64) {
|
||||
pub fn multiply(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint64, b: Uint64) {
|
||||
pub fn divide(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Uint64, b: Uint64) {
|
||||
pub fn divide_no_zero(a: Uint64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint64, b: Uint64) {
|
||||
pub fn modulo(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Uint64, b: Uint64) {
|
||||
pub fn modulo_no_zero(a: Uint64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint64, b: Uint64) {
|
||||
pub fn remainder(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Uint64, b: Uint64) {
|
||||
pub fn remainder_no_zero(a: Uint64, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint64, b: Uint64) {
|
||||
pub fn power(a: Uint64, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,43 +27,43 @@ pub fn compare(a: Uint8, b: Uint8) {
|
|||
interface.compare(a, b, iface)
|
||||
}
|
||||
|
||||
pub fn add(a: Uint8, b: Uint8) {
|
||||
pub fn add(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn subtract(a: Uint8, b: Uint8) {
|
||||
pub fn subtract(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.subtract)
|
||||
}
|
||||
|
||||
pub fn multiply(a: Uint8, b: Uint8) {
|
||||
pub fn multiply(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.multiply)
|
||||
}
|
||||
|
||||
pub fn divide(a: Uint8, b: Uint8) {
|
||||
pub fn divide(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.divide)
|
||||
}
|
||||
|
||||
pub fn divide_no_zero(a: Uint8, b: Uint8) {
|
||||
pub fn divide_no_zero(a: Uint8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.divide_no_zero)
|
||||
}
|
||||
|
||||
pub fn modulo(a: Uint8, b: Uint8) {
|
||||
pub fn modulo(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.modulo)
|
||||
}
|
||||
|
||||
pub fn modulo_no_zero(a: Uint8, b: Uint8) {
|
||||
pub fn modulo_no_zero(a: Uint8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.modulo_no_zero)
|
||||
}
|
||||
|
||||
pub fn remainder(a: Uint8, b: Uint8) {
|
||||
pub fn remainder(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.remainder)
|
||||
}
|
||||
|
||||
pub fn remainder_no_zero(a: Uint8, b: Uint8) {
|
||||
pub fn remainder_no_zero(a: Uint8, b: BigInt) {
|
||||
interface.fallible_op(a, b, iface, bigi.remainder_no_zero)
|
||||
}
|
||||
|
||||
pub fn power(a: Uint8, b: Uint8) {
|
||||
pub fn power(a: Uint8, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.power)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,48 +1,83 @@
|
|||
//// The ranged integer "interface".
|
||||
////
|
||||
//// This module contains types and functions to be implemented and used to
|
||||
//// create custom ranged integer types.
|
||||
////
|
||||
//// See the README for instructions on how to create your own type with this
|
||||
//// module.
|
||||
|
||||
import gleam/order
|
||||
import gleam/result
|
||||
import bigi.{type BigInt}
|
||||
import ranged_int/limit.{type Limit, NoOverflow, Overflow, Underflow}
|
||||
import ranged_int/internal/limit.{type Limit, NoOverflow, Overflow, Underflow}
|
||||
import ranged_int/utils
|
||||
|
||||
/// Mark this ranged integer as overflowable. An overflowable integer must have
|
||||
/// both a minimum and maximum limit.
|
||||
pub type Overflowable {
|
||||
Overflowable
|
||||
}
|
||||
|
||||
/// Mark this ranged integer as non-overflowable. A non-overflowable integer
|
||||
/// can only have a minimum or a maximum limit, but cannot use the `overflow`
|
||||
/// function.
|
||||
pub type NonOverflowable {
|
||||
NonOverflowable
|
||||
}
|
||||
|
||||
/// The minimum and maximum limits of a ranged integer.
|
||||
pub opaque type Limits(overflow_mode) {
|
||||
Limits(min: Limit, max: Limit)
|
||||
}
|
||||
|
||||
// Helper type for limits when we know that the integer is overflowable.
|
||||
type OverflowableLimits {
|
||||
OverflowableLimits(min: BigInt, max: BigInt)
|
||||
}
|
||||
|
||||
/// Result of math operations: a new ranged integer or an overflow.
|
||||
///
|
||||
/// An overflow result may occur even for non-overflowable integers. The
|
||||
/// difference is that the `overflow` function cannot be called to calculate the
|
||||
/// overflowed result for a non-overflowable integer.
|
||||
pub type OpResult(a) =
|
||||
Result(a, utils.Overflow)
|
||||
|
||||
/// The ranged integer interface that needs to be passed to math operations.
|
||||
///
|
||||
/// To allow for overflowing, the second type parameter must be `Overflowable`.
|
||||
pub type Interface(a, overflow_mode) {
|
||||
Interface(
|
||||
/// A function to convert the ranged integer into a big integer.
|
||||
to_bigint: fn(a) -> BigInt,
|
||||
/// A function to convert a big integer to the ranged integer. No limit
|
||||
/// checking is done, hence the name "unsafe". This is used internally when
|
||||
/// the limits have already been checked.
|
||||
from_bigint_unsafe: fn(BigInt) -> a,
|
||||
/// A function to return the limits of the ranged integer.
|
||||
limits: fn() -> Limits(overflow_mode),
|
||||
)
|
||||
}
|
||||
|
||||
/// Construct limits for an overflowable ranged integer. The given limits are
|
||||
/// inclusive.
|
||||
pub fn overflowable_limits(min: BigInt, max: BigInt) -> Limits(Overflowable) {
|
||||
Limits(min: limit.Bounded(min), max: limit.Bounded(max))
|
||||
}
|
||||
|
||||
/// Construct limits for a non-overflowable ranged integer that only has a
|
||||
/// maximum limit. The given limit is inclusive.
|
||||
pub fn max_limit(max: BigInt) -> Limits(NonOverflowable) {
|
||||
Limits(min: limit.Unbounded, max: limit.Bounded(max))
|
||||
}
|
||||
|
||||
/// Construct limits for a non-overflowable ranged integer that only has a
|
||||
/// minimum limit. The given limit is inclusive.
|
||||
pub fn min_limit(min: BigInt) -> Limits(NonOverflowable) {
|
||||
Limits(min: limit.Bounded(min), max: limit.Unbounded)
|
||||
}
|
||||
|
||||
/// Create a ranged integer from a big integer.
|
||||
pub fn from_bigint(
|
||||
value: BigInt,
|
||||
interface: Interface(a, overflow_mode),
|
||||
|
@ -55,6 +90,7 @@ pub fn from_bigint(
|
|||
}
|
||||
}
|
||||
|
||||
/// Compare two ranged integers.
|
||||
pub fn compare(
|
||||
int1: a,
|
||||
int2: a,
|
||||
|
@ -63,16 +99,16 @@ pub fn compare(
|
|||
bigi.compare(interface.to_bigint(int1), interface.to_bigint(int2))
|
||||
}
|
||||
|
||||
/// Run a math operation on a ranged integer and an unranged big integer.
|
||||
pub fn math_op(
|
||||
int1: a,
|
||||
int2: a,
|
||||
int2: BigInt,
|
||||
interface: Interface(a, overflow_mode),
|
||||
op: fn(BigInt, BigInt) -> BigInt,
|
||||
) -> OpResult(a) {
|
||||
let limits = interface.limits()
|
||||
let bigint1 = interface.to_bigint(int1)
|
||||
let bigint2 = interface.to_bigint(int2)
|
||||
let result = op(bigint1, bigint2)
|
||||
let int1 = interface.to_bigint(int1)
|
||||
let result = op(int1, int2)
|
||||
case limit.check_limits(result, min: limits.min, max: limits.max) {
|
||||
NoOverflow -> Ok(interface.from_bigint_unsafe(result))
|
||||
Overflow(amount) -> Error(utils.DidOverflow(amount))
|
||||
|
@ -80,16 +116,20 @@ pub fn math_op(
|
|||
}
|
||||
}
|
||||
|
||||
/// Run a math operation that may fail on a ranged integer and an unranged big
|
||||
/// integer.
|
||||
///
|
||||
/// Some functions such as `bigi.divide_no_zero` will return an error if the
|
||||
/// arguments are invalid. This error is passed upwards by this function.
|
||||
pub fn fallible_op(
|
||||
int1: a,
|
||||
int2: a,
|
||||
int2: BigInt,
|
||||
interface: Interface(a, overflow_mode),
|
||||
op: fn(BigInt, BigInt) -> Result(BigInt, op_err),
|
||||
) -> Result(OpResult(a), op_err) {
|
||||
let limits = interface.limits()
|
||||
let bigint1 = interface.to_bigint(int1)
|
||||
let bigint2 = interface.to_bigint(int2)
|
||||
use result <- result.try(op(bigint1, bigint2))
|
||||
let int1 = interface.to_bigint(int1)
|
||||
use result <- result.try(op(int1, int2))
|
||||
Ok(case limit.check_limits(result, min: limits.min, max: limits.max) {
|
||||
NoOverflow -> Ok(interface.from_bigint_unsafe(result))
|
||||
Overflow(amount) -> Error(utils.DidOverflow(amount))
|
||||
|
@ -97,6 +137,13 @@ pub fn fallible_op(
|
|||
})
|
||||
}
|
||||
|
||||
/// Map the overflow result of an operation to the integer's allowed range.
|
||||
///
|
||||
/// The overflow is calculated using a modulo against the amount of integers in
|
||||
/// the allowed range. This matches the unsigned integer overflow logic of C/C++
|
||||
/// but is also defined for signed (negative) values.
|
||||
///
|
||||
/// This can only be called for overflowable integers.
|
||||
pub fn overflow(value: OpResult(a), interface: Interface(a, Overflowable)) {
|
||||
case value {
|
||||
Ok(new_value) -> new_value
|
||||
|
@ -108,6 +155,8 @@ pub fn overflow(value: OpResult(a), interface: Interface(a, Overflowable)) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Eject the result of an operation to big integer. The big integer may be
|
||||
/// outside the range of the original ranged integers.
|
||||
pub fn eject(value: OpResult(a), interface: Interface(a, overflow_mode)) {
|
||||
case value {
|
||||
Ok(new_value) -> interface.to_bigint(new_value)
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import bigi.{type BigInt}
|
||||
|
||||
/// An overflow would have occurred, i.e. the result did not fit the allowed
|
||||
/// range.
|
||||
pub type Overflow {
|
||||
/// The result was this much higher than the maximum allowed value.
|
||||
DidOverflow(BigInt)
|
||||
/// The result was this much lower than the minimum allowed value.
|
||||
DidUnderflow(BigInt)
|
||||
}
|
||||
|
||||
/// Calculate the overflowed value given an overflow result and the limits.
|
||||
pub fn overflow(overflow: Overflow, min min: BigInt, max max: BigInt) -> BigInt {
|
||||
let total_values =
|
||||
bigi.subtract(max, min)
|
||||
|
|
|
@ -4,9 +4,21 @@ import ranged_int/builtin/generic
|
|||
|
||||
pub fn add_test() {
|
||||
let assert Ok(a) = generic.from_bigint_min(bigi.zero(), min: bigi.zero())
|
||||
let assert Ok(b) =
|
||||
generic.from_bigint_max(bigi.from_int(12), max: bigi.from_int(12))
|
||||
let interface = generic.get_interface(a)
|
||||
let c = generic.eject(generic.add(a, b), interface)
|
||||
let b = bigi.from_int(12)
|
||||
let iface = generic.get_interface(a)
|
||||
let c = generic.eject(generic.add(a, b), iface)
|
||||
should.equal(c, bigi.from_int(12))
|
||||
}
|
||||
|
||||
pub fn overflow_test() {
|
||||
let assert Ok(a) =
|
||||
generic.from_bigint_overflowable(
|
||||
bigi.from_int(-15),
|
||||
min: bigi.from_int(-284),
|
||||
max: bigi.from_int(-7),
|
||||
)
|
||||
let b = bigi.from_int(12)
|
||||
let iface = generic.get_interface(a)
|
||||
let c = generic.overflow(generic.add(a, b), iface)
|
||||
should.equal(generic.to_bigint(c), bigi.from_int(-281))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import ranged_int/builtin/uint16
|
|||
|
||||
pub fn add_test() {
|
||||
let assert Ok(a) = uint16.from_bigint(bigi.zero())
|
||||
let assert Ok(b) = uint16.from_bigint(bigi.from_int(12))
|
||||
let b = bigi.from_int(12)
|
||||
let c = uint16.eject(uint16.add(a, b))
|
||||
should.equal(c, bigi.from_int(12))
|
||||
}
|
||||
|
|
|
@ -4,14 +4,28 @@ import ranged_int/builtin/uint8
|
|||
|
||||
pub fn add_test() {
|
||||
let assert Ok(a) = uint8.from_bigint(bigi.zero())
|
||||
let assert Ok(b) = uint8.from_bigint(bigi.from_int(12))
|
||||
let b = bigi.from_int(12)
|
||||
let c = uint8.eject(uint8.add(a, b))
|
||||
should.equal(c, bigi.from_int(12))
|
||||
}
|
||||
|
||||
pub fn divide_test() {
|
||||
let assert Ok(a) = uint8.from_bigint(bigi.from_int(120))
|
||||
let assert Ok(b) = uint8.from_bigint(bigi.from_int(12))
|
||||
let b = bigi.from_int(12)
|
||||
let assert Ok(c) = uint8.divide_no_zero(a, b)
|
||||
should.equal(uint8.eject(c), bigi.from_int(10))
|
||||
}
|
||||
|
||||
pub fn overflow_test() {
|
||||
let assert Ok(a) = uint8.from_bigint(bigi.from_int(120))
|
||||
let b = bigi.from_int(160)
|
||||
let c = uint8.overflow(uint8.add(a, b))
|
||||
should.equal(uint8.to_bigint(c), bigi.from_int(24))
|
||||
}
|
||||
|
||||
pub fn underflow_test() {
|
||||
let assert Ok(a) = uint8.from_bigint(bigi.from_int(120))
|
||||
let b = bigi.from_int(160)
|
||||
let c = uint8.overflow(uint8.subtract(a, b))
|
||||
should.equal(uint8.to_bigint(c), bigi.from_int(216))
|
||||
}
|
||||
|
|
31
test/readme/mvp.gleam
Normal file
31
test/readme/mvp.gleam
Normal file
|
@ -0,0 +1,31 @@
|
|||
import bigi.{type BigInt}
|
||||
import ranged_int/interface.{type Interface, Interface}
|
||||
|
||||
const max_limit = 2015
|
||||
|
||||
const min_limit = 2007
|
||||
|
||||
pub opaque type Katzen {
|
||||
Katzen(data: BigInt)
|
||||
}
|
||||
|
||||
pub fn to_bigint(value: Katzen) {
|
||||
value.data
|
||||
}
|
||||
|
||||
pub fn limits() {
|
||||
interface.overflowable_limits(
|
||||
bigi.from_int(min_limit),
|
||||
bigi.from_int(max_limit),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_bigint_unsafe(value: BigInt) {
|
||||
Katzen(data: value)
|
||||
}
|
||||
|
||||
pub const iface: Interface(Katzen, interface.Overflowable) = Interface(
|
||||
from_bigint_unsafe: from_bigint_unsafe,
|
||||
to_bigint: to_bigint,
|
||||
limits: limits,
|
||||
)
|
43
test/readme/mvp2.gleam
Normal file
43
test/readme/mvp2.gleam
Normal file
|
@ -0,0 +1,43 @@
|
|||
import bigi.{type BigInt}
|
||||
import ranged_int/interface.{type Interface, Interface}
|
||||
|
||||
const max_limit = 2015
|
||||
|
||||
const min_limit = 2007
|
||||
|
||||
pub opaque type Katzen {
|
||||
Katzen(data: BigInt)
|
||||
}
|
||||
|
||||
pub fn to_bigint(value: Katzen) {
|
||||
value.data
|
||||
}
|
||||
|
||||
fn limits() {
|
||||
interface.overflowable_limits(
|
||||
bigi.from_int(min_limit),
|
||||
bigi.from_int(max_limit),
|
||||
)
|
||||
}
|
||||
|
||||
fn from_bigint_unsafe(value: BigInt) {
|
||||
Katzen(data: value)
|
||||
}
|
||||
|
||||
const iface: Interface(Katzen, interface.Overflowable) = Interface(
|
||||
from_bigint_unsafe: from_bigint_unsafe,
|
||||
to_bigint: to_bigint,
|
||||
limits: limits,
|
||||
)
|
||||
|
||||
pub fn add(a: Katzen, b: BigInt) {
|
||||
interface.math_op(a, b, iface, bigi.add)
|
||||
}
|
||||
|
||||
pub fn from_bigint(value: BigInt) {
|
||||
interface.from_bigint(value, iface)
|
||||
}
|
||||
|
||||
pub fn compare(a: Katzen, b: Katzen) {
|
||||
interface.compare(a, b, iface)
|
||||
}
|
19
test/readme/mvp2_test.gleam
Normal file
19
test/readme/mvp2_test.gleam
Normal file
|
@ -0,0 +1,19 @@
|
|||
import gleam/order
|
||||
import bigi
|
||||
import gleeunit/should
|
||||
import ranged_int/utils
|
||||
import readme/mvp2
|
||||
|
||||
pub fn mvp2_test() {
|
||||
let assert Ok(a) = mvp2.from_bigint(bigi.from_int(2007))
|
||||
let assert Ok(b) = mvp2.from_bigint(bigi.from_int(2009))
|
||||
let c = mvp2.compare(a, b)
|
||||
should.equal(c, order.Gt)
|
||||
}
|
||||
|
||||
pub fn mvp2_add_test() {
|
||||
let assert Ok(a) = mvp2.from_bigint(bigi.from_int(2007))
|
||||
let b = bigi.from_int(9)
|
||||
let c = mvp2.add(a, b)
|
||||
should.equal(c, Error(utils.DidOverflow(bigi.from_int(1))))
|
||||
}
|
20
test/readme/mvp_test.gleam
Normal file
20
test/readme/mvp_test.gleam
Normal file
|
@ -0,0 +1,20 @@
|
|||
import gleam/order
|
||||
import bigi
|
||||
import gleeunit/should
|
||||
import ranged_int/interface
|
||||
import ranged_int/utils
|
||||
import readme/mvp
|
||||
|
||||
pub fn mvp_test() {
|
||||
let assert Ok(a) = interface.from_bigint(bigi.from_int(2007), mvp.iface)
|
||||
let assert Ok(b) = interface.from_bigint(bigi.from_int(2009), mvp.iface)
|
||||
let c = interface.compare(a, b, mvp.iface)
|
||||
should.equal(c, order.Gt)
|
||||
}
|
||||
|
||||
pub fn mvp_add_test() {
|
||||
let assert Ok(a) = interface.from_bigint(bigi.from_int(2007), mvp.iface)
|
||||
let b = bigi.from_int(9)
|
||||
let c = interface.math_op(a, b, mvp.iface, bigi.add)
|
||||
should.equal(c, Error(utils.DidOverflow(bigi.from_int(1))))
|
||||
}
|
Loading…
Reference in a new issue