This commit is contained in:
Mikko Ahlroth 2024-02-08 22:07:36 +02:00
commit f616a9f6a8
11 changed files with 350 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump

25
README.md Normal file
View file

@ -0,0 +1,25 @@
# ranged_int
[![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
pub fn main() {
// TODO: An example of the project in use
}
```
Further documentation can be found at <https://hexdocs.pm/ranged_int>.
## Development
```sh
gleam run # Run the project
gleam test # Run the tests
gleam shell # Run an Erlang shell
```

20
gleam.toml Normal file
View file

@ -0,0 +1,20 @@
name = "ranged_int"
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" }
# links = [{ title = "Website", href = "https://gleam.run" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.
[dependencies]
gleam_stdlib = "~> 0.34 or ~> 1.0"
bigi = "~> 1.0"
[dev-dependencies]
gleeunit = "~> 1.0"

13
manifest.toml Normal file
View file

@ -0,0 +1,13 @@
# This file was generated by Gleam
# You typically do not need to edit this file
packages = [
{ name = "bigi", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "bigi", source = "hex", outer_checksum = "CB2766365C3DA3C651AD55F6A6389A586FA6B2D66B80BE0DA168EEBD0EE1A43D" },
{ name = "gleam_stdlib", version = "0.34.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1FB8454D2991E9B4C0C804544D8A9AD0F6184725E20D63C3155F0AEB4230B016" },
{ name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" },
]
[requirements]
bigi = { version = "~> 1.0"}
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }

13
src/ranged_int.gleam Normal file
View file

@ -0,0 +1,13 @@
import gleam/io
import bigi
import ranged_int/builtin/uint8
import ranged_int/builtin/uint
import ranged_int/utils
pub fn main() {
io.debug(utils.overflow(
utils.DidUnderflow(bigi.from_int(12)),
bigi.from_int(-15),
bigi.from_int(-126_463),
))
}

View file

@ -0,0 +1,31 @@
import bigi.{type BigInt}
import ranged_int/interface.{type Interface, Interface, Limits}
import ranged_int/limit.{Bounded, Unbounded}
pub const min_limit = 0
pub opaque type Uint {
Uint(data: BigInt)
}
const uint_interface: Interface(Uint, interface.NonOverflowable) = Interface(
from_bigint_unsafe: from_bigint_unsafe,
to_bigint: to_bigint,
limits: limits,
)
pub fn from_bigint(value: BigInt) {
interface.from_bigint(value, uint_interface)
}
fn limits() {
Limits(max: Unbounded, min: Bounded(bigi.from_int(min_limit)))
}
fn from_bigint_unsafe(value: BigInt) {
Uint(data: value)
}
fn to_bigint(uint: Uint) {
uint.data
}

View file

@ -0,0 +1,40 @@
import bigi.{type BigInt}
import ranged_int/interface.{type Interface, Interface, Limits}
import ranged_int/limit.{Bounded}
pub const max_limit = 255
pub const min_limit = 0
pub opaque type Uint8 {
Uint8(data: BigInt)
}
const uint8_interface: Interface(Uint8, interface.Overflowable) = Interface(
from_bigint_unsafe: from_bigint_unsafe,
to_bigint: to_bigint,
limits: limits,
)
pub fn from_bigint(value: BigInt) {
interface.from_bigint(value, uint8_interface)
}
pub fn from_bigint_overflow(value: BigInt) {
interface.from_bigint_overflow(value, uint8_interface)
}
fn limits() {
Limits(
max: Bounded(bigi.from_int(max_limit)),
min: Bounded(bigi.from_int(min_limit)),
)
}
fn from_bigint_unsafe(value: BigInt) {
Uint8(data: value)
}
fn to_bigint(uint: Uint8) {
uint.data
}

View file

@ -0,0 +1,123 @@
import gleam/order
import bigi.{type BigInt}
import ranged_int/limit.{type Limit, NoOverflow, Overflow, Underflow}
import ranged_int/utils
type Constructor(a) =
fn(BigInt) -> a
pub type Overflowable {
Overflowable
}
pub type NonOverflowable {
NonOverflowable
}
pub opaque type IntType(overflow_mode) {
IntType
}
pub type Limits {
Limits(max: Limit, min: Limit)
}
pub type MathOp =
fn(BigInt, BigInt) -> BigInt
pub type Interface(a, overflow_mode) {
Interface(
to_bigint: fn(a) -> BigInt,
from_bigint_unsafe: Constructor(a),
limits: fn() -> Limits,
)
}
pub const mode_overflowable: IntType(Overflowable) = IntType
pub const mode_non_overflowable: IntType(NonOverflowable) = IntType
pub fn from_bigint(
value: BigInt,
interface: Interface(a, overflow_mode),
) -> Result(a, utils.Overflow) {
let limits = interface.limits()
case limit.check_limits(value, limits.max, limits.min) {
NoOverflow -> Ok(interface.from_bigint_unsafe(value))
Overflow(amount) -> Error(utils.DidOverflow(amount))
Underflow(amount) -> Error(utils.DidUnderflow(amount))
}
}
pub fn from_bigint_overflow(
value: BigInt,
interface: Interface(a, Overflowable),
) -> a {
handle_overflow(value, interface)
}
pub fn compare(
int1: a,
int2: a,
interface: Interface(a, overflow_mode),
) -> order.Order {
bigi.compare(interface.to_bigint(int1), interface.to_bigint(int2))
}
pub fn math_op(
int1: a,
int2: a,
interface: Interface(a, overflow_mode),
op: MathOp,
) -> Result(a, utils.Overflow) {
let limits = interface.limits()
let bigint1 = interface.to_bigint(int1)
let bigint2 = interface.to_bigint(int2)
let result = op(bigint1, bigint2)
case limit.check_limits(result, limits.max, limits.min) {
NoOverflow -> Ok(interface.from_bigint_unsafe(result))
Overflow(amount) -> Error(utils.DidOverflow(amount))
Underflow(amount) -> Error(utils.DidUnderflow(amount))
}
}
pub fn math_op_overflow(
int1: a,
int2: a,
interface: Interface(a, Overflowable),
op: MathOp,
) -> a {
let bigint1 = interface.to_bigint(int1)
let bigint2 = interface.to_bigint(int2)
let result = op(bigint1, bigint2)
handle_overflow(result, interface)
}
pub fn math_op_eject(
int1: a,
int2: a,
interface: Interface(a, overflow_mode),
op: MathOp,
) -> BigInt {
let bigint1 = interface.to_bigint(int1)
let bigint2 = interface.to_bigint(int2)
op(bigint1, bigint2)
}
pub fn handle_overflow(value: BigInt, interface: Interface(a, Overflowable)) {
let limits = interface.limits()
let result = case limit.check_limits(value, limits.max, limits.min) {
NoOverflow -> value
Overflow(amount) -> {
let assert limit.Bounded(max) = limits.max
let assert limit.Bounded(min) = limits.min
utils.overflow(utils.DidOverflow(amount), max, min)
}
Underflow(amount) -> {
let assert limit.Bounded(max) = limits.max
let assert limit.Bounded(min) = limits.min
utils.overflow(utils.DidUnderflow(amount), max, min)
}
}
interface.from_bigint_unsafe(result)
}

View file

@ -0,0 +1,45 @@
import gleam/order
import bigi.{type BigInt}
/// A limit for an integer. Each direction (max, min) may have its own limit.
pub type Limit {
/// The integer is limited to this value inclusive.
Bounded(BigInt)
/// The integer is unbounded.
Unbounded
}
/// Result of a limit check.
pub type LimitCheck {
/// No overflow was detected.
NoOverflow
/// The amount would overflow the allowed range by this amount.
Overflow(BigInt)
/// The amount would underflow (be lower than the minimum) by this amount.
Underflow(BigInt)
}
/// Check the value against the limits.
pub fn check_limits(value: BigInt, max max: Limit, min min: Limit) -> LimitCheck {
let max_diff = diff(max, value)
case bigi.compare(bigi.zero(), max_diff) {
order.Gt -> Overflow(max_diff)
order.Eq | order.Lt -> {
let min_diff = diff(min, value)
case bigi.compare(bigi.zero(), min_diff) {
order.Lt -> Underflow(bigi.multiply(min_diff, bigi.from_int(-1)))
order.Eq | order.Gt -> NoOverflow
}
}
}
}
fn diff(limit: Limit, value: BigInt) {
case limit {
Unbounded -> bigi.zero()
Bounded(lim) -> bigi.subtract(value, lim)
}
}

View file

@ -0,0 +1,24 @@
import bigi.{type BigInt}
pub type Overflow {
DidOverflow(BigInt)
DidUnderflow(BigInt)
}
pub fn overflow(overflow: Overflow, max: BigInt, min: BigInt) -> BigInt {
let total_values =
bigi.subtract(max, min)
|> bigi.add(bigi.from_int(1))
let min_adjustment_factor = bigi.multiply(min, bigi.from_int(-1))
let adjusted_max = bigi.add(max, min_adjustment_factor)
let adjusted_min = bigi.zero()
let overflowed_value = case overflow {
DidOverflow(amount) -> bigi.add(adjusted_max, amount)
DidUnderflow(amount) -> bigi.subtract(adjusted_min, amount)
}
let overflow_result = bigi.modulo(overflowed_value, total_values)
bigi.subtract(overflow_result, min_adjustment_factor)
}

View file

@ -0,0 +1,12 @@
import gleeunit
import gleeunit/should
pub fn main() {
gleeunit.main()
}
// gleeunit test functions end in `_test`
pub fn hello_world_test() {
1
|> should.equal(1)
}