Merge branch 'byte_conversions' into 'trunk'
Add bigi.from_bytes() and bigi.to_bytes() Closes #1 See merge request Nicd/bigi!1
This commit is contained in:
commit
961096d42e
5 changed files with 238 additions and 8 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
Unreleased
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Added bigi.from_bytes() and bigi.to_bytes() functions.
|
||||||
|
|
||||||
3.0.0
|
3.0.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,23 @@
|
||||||
import gleam/order
|
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
|
import gleam/order
|
||||||
|
|
||||||
/// A big integer.
|
/// A big integer.
|
||||||
pub type BigInt
|
pub type BigInt
|
||||||
|
|
||||||
|
/// Endianness specifier used when converting between a big integer and
|
||||||
|
/// raw bytes.
|
||||||
|
pub type Endianness {
|
||||||
|
LittleEndian
|
||||||
|
BigEndian
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signedness specifier used when converting between a big integer and
|
||||||
|
/// raw bytes.
|
||||||
|
pub type Signedness {
|
||||||
|
Signed
|
||||||
|
Unsigned
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a big integer representing zero.
|
/// Create a big integer representing zero.
|
||||||
@external(erlang, "bigi_ffi", "zero")
|
@external(erlang, "bigi_ffi", "zero")
|
||||||
@external(javascript, "./bigi_ffi.mjs", "zero")
|
@external(javascript, "./bigi_ffi.mjs", "zero")
|
||||||
|
@ -30,6 +44,18 @@ pub fn from_int(int: Int) -> BigInt
|
||||||
@external(javascript, "./bigi_ffi.mjs", "from_string")
|
@external(javascript, "./bigi_ffi.mjs", "from_string")
|
||||||
pub fn from_string(str: String) -> Result(BigInt, Nil)
|
pub fn from_string(str: String) -> Result(BigInt, Nil)
|
||||||
|
|
||||||
|
/// Convert raw bytes into a big integer.
|
||||||
|
///
|
||||||
|
/// If the bit array does not contain a whole number of bytes then an error is
|
||||||
|
/// returned.
|
||||||
|
@external(erlang, "bigi_ffi", "from_bytes")
|
||||||
|
@external(javascript, "./bigi_ffi.mjs", "from_bytes")
|
||||||
|
pub fn from_bytes(
|
||||||
|
bytes: BitArray,
|
||||||
|
endianness: Endianness,
|
||||||
|
signedness: Signedness,
|
||||||
|
) -> Result(BigInt, Nil)
|
||||||
|
|
||||||
/// Convert a big integer to a regular integer.
|
/// Convert a big integer to a regular integer.
|
||||||
///
|
///
|
||||||
/// In Erlang, this cannot fail, as all Erlang integers are big integers. In the
|
/// In Erlang, this cannot fail, as all Erlang integers are big integers. In the
|
||||||
|
@ -46,6 +72,20 @@ pub fn to_int(bigint: BigInt) -> Result(Int, Nil)
|
||||||
@external(javascript, "./bigi_ffi.mjs", "to_string")
|
@external(javascript, "./bigi_ffi.mjs", "to_string")
|
||||||
pub fn to_string(bigint: BigInt) -> String
|
pub fn to_string(bigint: BigInt) -> String
|
||||||
|
|
||||||
|
/// Convert a big integer to raw bytes.
|
||||||
|
///
|
||||||
|
/// The size of the returned bit array is specified by `byte_count`, e.g. 8 will
|
||||||
|
/// return a bit array containing 8 bytes (64 bits). If the big integer doesn't
|
||||||
|
/// fit in the specified number of bytes then an error is returned.
|
||||||
|
@external(erlang, "bigi_ffi", "to_bytes")
|
||||||
|
@external(javascript, "./bigi_ffi.mjs", "to_bytes")
|
||||||
|
pub fn to_bytes(
|
||||||
|
bigint: BigInt,
|
||||||
|
endianness: Endianness,
|
||||||
|
signedness: Signedness,
|
||||||
|
byte_count: Int,
|
||||||
|
) -> Result(BitArray, Nil)
|
||||||
|
|
||||||
/// Compare two big integers, returning an order that denotes if `a` is lower,
|
/// Compare two big integers, returning an order that denotes if `a` is lower,
|
||||||
/// bigger than, or equal to `b`.
|
/// bigger than, or equal to `b`.
|
||||||
@external(erlang, "bigi_ffi", "compare")
|
@external(erlang, "bigi_ffi", "compare")
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
-export([
|
-export([
|
||||||
from/1,
|
from/1,
|
||||||
from_string/1,
|
from_string/1,
|
||||||
|
from_bytes/3,
|
||||||
to/1,
|
to/1,
|
||||||
|
to_bytes/4,
|
||||||
zero/0,
|
zero/0,
|
||||||
compare/2,
|
compare/2,
|
||||||
add/2,
|
add/2,
|
||||||
|
@ -27,8 +29,60 @@ from_string(Str) ->
|
||||||
{Int, _} -> {ok, Int}
|
{Int, _} -> {ok, Int}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
from_bytes(Bytes, Endianness, Signedness) ->
|
||||||
|
BitSize = erlang:bit_size(Bytes),
|
||||||
|
|
||||||
|
case BitSize rem 8 of
|
||||||
|
0 -> case Endianness of
|
||||||
|
little_endian -> case Signedness of
|
||||||
|
signed ->
|
||||||
|
<<Int:BitSize/little-signed-integer>> = Bytes,
|
||||||
|
{ok, Int};
|
||||||
|
unsigned ->
|
||||||
|
<<Int:BitSize/little-unsigned-integer>> = Bytes,
|
||||||
|
{ok, Int}
|
||||||
|
end;
|
||||||
|
big_endian -> case Signedness of
|
||||||
|
signed ->
|
||||||
|
<<Int:BitSize/big-signed-integer>> = Bytes,
|
||||||
|
{ok, Int};
|
||||||
|
unsigned ->
|
||||||
|
<<Int:BitSize/big-unsigned-integer>> = Bytes,
|
||||||
|
{ok, Int}
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
|
||||||
|
_ -> {error, nil}
|
||||||
|
end.
|
||||||
|
|
||||||
to(BigInt) -> {ok, BigInt}.
|
to(BigInt) -> {ok, BigInt}.
|
||||||
|
|
||||||
|
to_bytes(BigInt, Endianness, Signedness, ByteCount) ->
|
||||||
|
case ByteCount * 8 of
|
||||||
|
BitCount when BitCount >= 8 ->
|
||||||
|
RangeMin = case Signedness of
|
||||||
|
signed -> -(1 bsl (BitCount - 1));
|
||||||
|
unsigned -> 0
|
||||||
|
end,
|
||||||
|
|
||||||
|
RangeMax = case Signedness of
|
||||||
|
signed -> (1 bsl (BitCount - 1)) - 1;
|
||||||
|
unsigned -> (1 bsl BitCount) - 1
|
||||||
|
end,
|
||||||
|
|
||||||
|
% Error if the value is out of range for the available bits
|
||||||
|
case BigInt >= RangeMin andalso BigInt =< RangeMax of
|
||||||
|
true ->
|
||||||
|
case Endianness of
|
||||||
|
little_endian -> {ok, <<BigInt:BitCount/little-integer>> };
|
||||||
|
big_endian -> {ok, <<BigInt:BitCount/big-integer>>}
|
||||||
|
end;
|
||||||
|
|
||||||
|
false -> {error, nil}
|
||||||
|
end;
|
||||||
|
|
||||||
|
_ -> {error, nil}
|
||||||
|
end.
|
||||||
zero() -> 0.
|
zero() -> 0.
|
||||||
|
|
||||||
compare(A, B) when A < B -> lt;
|
compare(A, B) when A < B -> lt;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Ok, Error, toList } from "./gleam.mjs";
|
import { Ok, Error, toList, BitArray } from "./gleam.mjs";
|
||||||
import { Lt, Eq, Gt } from "../gleam_stdlib/gleam/order.mjs";
|
import { Lt, Eq, Gt } from "../gleam_stdlib/gleam/order.mjs";
|
||||||
import { DecodeError } from "../gleam_stdlib/gleam/dynamic.mjs";
|
import { DecodeError } from "../gleam_stdlib/gleam/dynamic.mjs";
|
||||||
|
import { BigEndian, Signed } from "./bigi.mjs";
|
||||||
|
|
||||||
export function from(int) {
|
export function from(int) {
|
||||||
return BigInt(int);
|
return BigInt(int);
|
||||||
|
@ -14,6 +15,35 @@ export function from_string(string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function from_bytes(bit_array, endianness, signedness) {
|
||||||
|
let value = 0n;
|
||||||
|
|
||||||
|
// Read bytes as an unsigned integer value
|
||||||
|
if (endianness instanceof BigEndian) {
|
||||||
|
for (let i = 0; i < bit_array.length; i++) {
|
||||||
|
value = value * 256n + BigInt(bit_array.byteAt(i));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = bit_array.length - 1; i >= 0; i--) {
|
||||||
|
value = value * 256n + BigInt(bit_array.byteAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signedness instanceof Signed) {
|
||||||
|
const byteSize = BigInt(bit_array.length);
|
||||||
|
|
||||||
|
const highBit = 2n ** (byteSize * 8n - 1n);
|
||||||
|
|
||||||
|
// If the high bit is set and this is a signed integer, reinterpret as
|
||||||
|
// two's complement
|
||||||
|
if (value >= highBit) {
|
||||||
|
value -= highBit * 2n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Ok(value);
|
||||||
|
}
|
||||||
|
|
||||||
export function to(bigint) {
|
export function to(bigint) {
|
||||||
if (bigint > Number.MAX_SAFE_INTEGER || bigint < Number.MIN_SAFE_INTEGER) {
|
if (bigint > Number.MAX_SAFE_INTEGER || bigint < Number.MIN_SAFE_INTEGER) {
|
||||||
return new Error(undefined);
|
return new Error(undefined);
|
||||||
|
@ -26,6 +56,52 @@ export function to_string(bigint) {
|
||||||
return bigint.toString();
|
return bigint.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function to_bytes(bigint, endianness, signedness, byte_count) {
|
||||||
|
const bit_count = BigInt(byte_count * 8);
|
||||||
|
|
||||||
|
if (bit_count < 8n) {
|
||||||
|
return new Error(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
let range_min = 0n;
|
||||||
|
let range_max = 0n;
|
||||||
|
|
||||||
|
// Error if the value is out of range for the available bits
|
||||||
|
if (signedness instanceof Signed) {
|
||||||
|
range_min = -(2n ** (bit_count - 1n));
|
||||||
|
range_max = -range_min - 1n;
|
||||||
|
} else {
|
||||||
|
range_max = 2n ** bit_count - 1n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bigint < range_min || bigint > range_max) {
|
||||||
|
return new Error(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert negative number to two's complement representation
|
||||||
|
if (bigint < 0) {
|
||||||
|
bigint = (1n << bit_count) + bigint;
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(byte_count);
|
||||||
|
|
||||||
|
if (endianness instanceof BigEndian) {
|
||||||
|
for (let i = byteArray.length - 1; i >= 0; i--) {
|
||||||
|
const byte = bigint % 256n;
|
||||||
|
byteArray[i] = Number(byte);
|
||||||
|
bigint = (bigint - byte) / 256n;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < byteArray.length; i++) {
|
||||||
|
const byte = bigint % 256n;
|
||||||
|
byteArray[i] = Number(byte);
|
||||||
|
bigint = (bigint - byte) / 256n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Ok(new BitArray(byteArray));
|
||||||
|
}
|
||||||
|
|
||||||
export function zero() {
|
export function zero() {
|
||||||
return 0n;
|
return 0n;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import gleam/order
|
import bigi
|
||||||
|
import gleam/bit_array
|
||||||
import gleam/dynamic
|
import gleam/dynamic
|
||||||
|
import gleam/list
|
||||||
|
import gleam/order
|
||||||
import gleeunit
|
import gleeunit
|
||||||
import gleeunit/should
|
import gleeunit/should
|
||||||
import bigi
|
|
||||||
|
|
||||||
const js_max_safe_int = 9_007_199_254_740_991
|
const js_max_safe_int = 9_007_199_254_740_991
|
||||||
|
|
||||||
|
@ -174,6 +176,59 @@ pub fn from_bad_string_test() {
|
||||||
should.be_error(parsed)
|
should.be_error(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes_test() {
|
||||||
|
[
|
||||||
|
#("4328719365", <<1, 2, 3, 4, 5>>, bigi.BigEndian, bigi.Unsigned),
|
||||||
|
#(
|
||||||
|
"-4685255690516759574262",
|
||||||
|
<<255, 2, 3, 4, 5, 6, 7, 8, 9, 10>>,
|
||||||
|
bigi.BigEndian,
|
||||||
|
bigi.Signed,
|
||||||
|
),
|
||||||
|
#("21542142465", <<1, 2, 3, 4, 5>>, bigi.LittleEndian, bigi.Unsigned),
|
||||||
|
#(
|
||||||
|
"-4555767348510506941951",
|
||||||
|
<<1, 2, 3, 4, 5, 6, 7, 8, 9, 255>>,
|
||||||
|
bigi.LittleEndian,
|
||||||
|
bigi.Signed,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|> list.each(fn(x) {
|
||||||
|
let #(bigi_string, bytes, endianness, signedness) = x
|
||||||
|
|
||||||
|
let assert Ok(i) = bigi.from_string(bigi_string)
|
||||||
|
|
||||||
|
bigi.from_bytes(bytes, endianness, signedness)
|
||||||
|
|> should.equal(Ok(i))
|
||||||
|
|
||||||
|
bigi.to_bytes(i, endianness, signedness, bit_array.byte_size(bytes))
|
||||||
|
|> should.equal(Ok(bytes))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check error is returned when the integer is out of range
|
||||||
|
bigi.to_bytes(
|
||||||
|
bigi.from_int(4_294_967_296),
|
||||||
|
bigi.LittleEndian,
|
||||||
|
bigi.Unsigned,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
|> should.equal(Error(Nil))
|
||||||
|
|
||||||
|
bigi.to_bytes(bigi.from_int(-1), bigi.LittleEndian, bigi.Unsigned, 4)
|
||||||
|
|> should.equal(Error(Nil))
|
||||||
|
|
||||||
|
bigi.to_bytes(bigi.from_int(2_147_483_648), bigi.LittleEndian, bigi.Signed, 4)
|
||||||
|
|> should.equal(Error(Nil))
|
||||||
|
|
||||||
|
bigi.to_bytes(
|
||||||
|
bigi.from_int(-2_147_483_649),
|
||||||
|
bigi.LittleEndian,
|
||||||
|
bigi.Signed,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
|> should.equal(Error(Nil))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn power_test() {
|
pub fn power_test() {
|
||||||
let assert Ok(bigint) = bigi.power(bigi.from_int(2), bigi.from_int(65_535))
|
let assert Ok(bigint) = bigi.power(bigi.from_int(2), bigi.from_int(65_535))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue