Files
2026-09-18 11:17:33 +08:00

1644 lines
55 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Expect retcode = 0
// 多精度整数模块:BigInt 表示数学整数,Bits 表示任意正位宽的位串。
// limbs 为小端 u32;BigInt 的零规范为 len=0 且 negative=false。
// 普通赋值只借用同一存储;深复制使用 clone,所有权转移使用 take。
type .{ .Error, .Status } = @import("error.spl");
type .{ .checked_mul } = @import("memory.spl");
type .{ .digit_char, .digit_value } = @import("ascii.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
type BigInt = @self;
limbs: *u32,
len: usize,
cap: usize,
negative: bool,
fn zero(): BigInt {
ret BigInt { .limbs = null, .len = 0, .cap = 0, .negative = false };
}
fn valid(self: BigInt): bool {
if self.len > self.cap { ret false; }
if self.cap == 0 {
ret self.limbs == null && self.len == 0 && !self.negative;
}
if self.limbs == null { ret false; }
if self.len == 0 { ret !self.negative; }
ret self.limbs[self.len - 1] != 0;
}
fn is_zero(self: BigInt): bool {
ret self.len == 0;
}
fn is_negative(self: BigInt): bool {
ret self.negative;
}
fn normalize(self: *BigInt) {
while self.len > 0 {
if self.limbs[self.len - 1] != 0 { break; }
self.len = self.len - 1;
}
if self.len == 0 { self.negative = false; }
}
fn reserve(self: *BigInt, need: usize): Status {
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
if need <= self.cap { ret .Ok; }
var new_cap = self.cap;
if new_cap < 2 { new_cap = 2; }
while new_cap < need {
if new_cap > (~(0 as usize)) / 2 {
new_cap = need;
break;
}
new_cap = new_cap * 2;
}
var byte_count: usize = 0;
match checked_mul(new_cap, @sizeof(u32)) {
.Ok(value) => { byte_count = value; },
.Err(error) => { ret .Err(error); },
}
var fresh = alloc(byte_count) as *u32;
if fresh == null {
ret .Err(Error.make(.Overflow));
}
if self.len != 0 {
copy(fresh as *any, self.limbs as *any, self.len * @sizeof(u32));
}
free(self.limbs as *any);
self.limbs = fresh;
self.cap = new_cap;
ret .Ok;
}
fn drop(self: *BigInt) {
free(self.limbs as *any);
self.* = zero();
}
fn take(source: *BigInt): BigInt {
var result = source.*;
source.* = zero();
ret result;
}
fn replace(out: *BigInt, fresh: *BigInt) {
drop(out);
out.* = take(fresh);
}
fn clone(out: *BigInt, source: BigInt): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var fresh = zero();
match reserve(&fresh, source.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if source.len != 0 {
copy(fresh.limbs as *any, source.limbs as *any, source.len * @sizeof(u32));
}
fresh.len = source.len;
fresh.negative = source.negative;
replace(out, &fresh);
ret .Ok;
}
fn from_u64(out: *BigInt, value: u64): Status {
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
var fresh = zero();
if value != 0 {
match reserve(&fresh, 2) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
fresh.limbs[0] = value as u32;
fresh.limbs[1] = (value >> 32) as u32;
fresh.len = 1;
if fresh.limbs[1] != 0 { fresh.len = 2; }
}
replace(out, &fresh);
ret .Ok;
}
fn from_i64(out: *BigInt, value: i64): Status {
var magnitude = value as u64;
var negative = value < 0;
if negative { magnitude = 0 - magnitude; }
match from_u64(out, magnitude) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if out.len != 0 { out.negative = negative; }
ret .Ok;
}
fn cmp_abs(a: BigInt, b: BigInt): i32 {
if a.len < b.len { ret -1; }
if a.len > b.len { ret 1; }
var i = a.len;
while i > 0 {
i = i - 1;
if a.limbs[i] < b.limbs[i] { ret -1; }
if a.limbs[i] > b.limbs[i] { ret 1; }
}
ret 0;
}
fn cmp(a: BigInt, b: BigInt): i32 {
if a.negative != b.negative {
if a.negative { ret -1; }
ret 1;
}
var order = cmp_abs(a, b);
if a.negative { ret 0 - order; }
ret order;
}
fn eq(a: BigInt, b: BigInt): bool {
if a.negative != b.negative || a.len != b.len { ret false; }
var i: usize = 0;
while i < a.len {
if a.limbs[i] != b.limbs[i] { ret false; }
i = i + 1;
}
ret true;
}
fn add_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
var count = a.len;
if b.len > count { count = b.len; }
if count == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(out, count + 1) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var carry: u64 = 0;
var i: usize = 0;
while i < count {
var av: u64 = 0;
var bv: u64 = 0;
if i < a.len { av = a.limbs[i] as u64; }
if i < b.len { bv = b.limbs[i] as u64; }
var sum = av + bv + carry;
out.limbs[i] = sum as u32;
carry = sum >> 32;
i = i + 1;
}
if carry != 0 {
out.limbs[count] = carry as u32;
count = count + 1;
}
out.len = count;
out.negative = false;
normalize(out);
ret .Ok;
}
// 要求 |a| >= |b|。
fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
match reserve(out, a.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var borrow: u64 = 0;
var base: u64 = 4294967296; // 2^32
var i: usize = 0;
while i < a.len {
var av = a.limbs[i] as u64;
var bv: u64 = 0;
if i < b.len { bv = b.limbs[i] as u64; }
var subtrahend = bv + borrow;
if av < subtrahend {
out.limbs[i] = (base + av - subtrahend) as u32;
borrow = 1;
} else {
out.limbs[i] = (av - subtrahend) as u32;
borrow = 0;
}
i = i + 1;
}
out.len = a.len;
out.negative = false;
normalize(out);
ret .Ok;
}
fn add(out: *BigInt, a: BigInt, b: BigInt): Status {
if !valid(out.*) || !valid(a) || !valid(b) {
ret .Err(Error.make(.InvalidArgument));
}
var fresh = zero();
if a.negative == b.negative {
match add_abs_into(&fresh, a, b) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = a.negative; }
} else {
var order = cmp_abs(a, b);
if order >= 0 {
match sub_abs_into(&fresh, a, b) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = a.negative; }
} else {
match sub_abs_into(&fresh, b, a) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = b.negative; }
}
}
replace(out, &fresh);
ret .Ok;
}
fn sub(out: *BigInt, a: BigInt, b: BigInt): Status {
var neg_b = b;
if neg_b.len != 0 { neg_b.negative = !neg_b.negative; }
ret add(out, a, neg_b);
}
fn neg(out: *BigInt, source: BigInt): Status {
match clone(out, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if out.len != 0 { out.negative = !out.negative; }
ret .Ok;
}
fn abs(out: *BigInt, source: BigInt): Status {
match clone(out, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
out.negative = false;
ret .Ok;
}
fn mul(out: *BigInt, a: BigInt, b: BigInt): Status {
if !valid(out.*) || !valid(a) || !valid(b) {
ret .Err(Error.make(.InvalidArgument));
}
var fresh = zero();
if a.len == 0 || b.len == 0 {
replace(out, &fresh);
ret .Ok;
}
if b.len > (~(0 as usize)) - a.len {
ret .Err(Error.make(.Overflow));
}
var count = a.len + b.len;
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var z: usize = 0;
while z < count { fresh.limbs[z] = 0; z = z + 1; }
var i: usize = 0;
while i < a.len {
var carry: u64 = 0;
var j: usize = 0;
while j < b.len {
var at = i + j;
var product = (a.limbs[i] as u64) * (b.limbs[j] as u64) +
(fresh.limbs[at] as u64) + carry;
fresh.limbs[at] = product as u32;
carry = product >> 32;
j = j + 1;
}
fresh.limbs[i + b.len] = carry as u32;
i = i + 1;
}
fresh.len = count;
fresh.negative = a.negative != b.negative;
normalize(&fresh);
replace(out, &fresh);
ret .Ok;
}
fn bit_length(out: *usize, self: BigInt): Status {
if out == null || !valid(self) { ret .Err(Error.make(.InvalidArgument)); }
if self.len == 0 { out.* = 0; ret .Ok; }
if self.len - 1 > (~(0 as usize)) / 32 { ret .Err(Error.make(.Overflow)); }
var top = self.limbs[self.len - 1];
var bits = (self.len - 1) * 32;
while top != 0 {
bits = bits + 1;
top = top >> 1;
}
out.* = bits;
ret .Ok;
}
fn mul_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.len == 0 { ret from_u64(out, 0); }
var words = amount / 32;
var shift = amount % 32;
if source.len == ~(0 as usize) || words > (~(0 as usize)) - source.len - 1 {
ret .Err(Error.make(.Overflow));
}
var fresh = zero();
var count = source.len + words + 1;
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < words { fresh.limbs[i] = 0; i = i + 1; }
var carry: u64 = 0;
i = 0;
while i < source.len {
var value = ((source.limbs[i] as u64) << shift as u32) | carry;
fresh.limbs[i + words] = value as u32;
carry = value >> 32;
i = i + 1;
}
fresh.len = source.len + words;
if carry != 0 {
fresh.limbs[fresh.len] = carry as u32;
fresh.len = fresh.len + 1;
}
fresh.negative = source.negative;
replace(out, &fresh);
ret .Ok;
}
// 按绝对值右移,等价于向零除以 2^amount。
fn div_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var words = amount / 32;
var shift = amount % 32;
if words >= source.len { ret from_u64(out, 0); }
var count = source.len - words;
var fresh = zero();
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < count {
var src = i + words;
var value = (source.limbs[src] as u64) >> shift as u32;
if shift != 0 && src + 1 < source.len {
value = value | ((source.limbs[src + 1] << (32 - shift) as u32) as u64);
}
fresh.limbs[i] = value as u32;
i = i + 1;
}
fresh.len = count;
fresh.negative = source.negative;
normalize(&fresh);
replace(out, &fresh);
ret .Ok;
}
fn shift_one(self: *BigInt) {
var carry: u64 = 0;
var i: usize = 0;
while i < self.len {
var value = ((self.limbs[i] as u64) << 1) | carry;
self.limbs[i] = value as u32;
carry = value >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
}
fn sub_abs_in_place(a: *BigInt, b: BigInt) {
var borrow: u64 = 0;
var base: u64 = 4294967296; // 2^32
var i: usize = 0;
while i < a.len {
var av = a.limbs[i] as u64;
var bv: u64 = 0;
if i < b.len { bv = b.limbs[i] as u64; }
var subtrahend = bv + borrow;
if av < subtrahend {
a.limbs[i] = (base + av - subtrahend) as u32;
borrow = 1;
} else {
a.limbs[i] = (av - subtrahend) as u32;
borrow = 0;
}
i = i + 1;
}
normalize(a);
}
fn div_rem(quotient: *BigInt, remainder: *BigInt, dividend: BigInt,
divisor: BigInt): Status {
if (quotient as usize) == (remainder as usize) {
ret .Err(Error.make(.InvalidArgument));
}
if !valid(quotient.*) || !valid(remainder.*) || !valid(dividend) || !valid(divisor) {
ret .Err(Error.make(.InvalidArgument));
}
if divisor.len == 0 { ret .Err(Error.make(.DivisionByZero)); }
var q = zero();
var r = zero();
match reserve(&q, dividend.len) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if divisor.len == ~(0 as usize) { drop(&q); ret .Err(Error.make(.Overflow)); }
match reserve(&r, divisor.len + 1) {
.Ok => {}, .Err(error) => { drop(&q); ret .Err(error); },
}
var i: usize = 0;
while i < dividend.len { q.limbs[i] = 0; i = i + 1; }
q.len = dividend.len;
var bit: usize = 0;
match bit_length(&bit, dividend) {
.Ok => {},
.Err(error) => { drop(&q); drop(&r); ret .Err(error); },
}
while bit > 0 {
bit = bit - 1;
shift_one(&r);
var word = bit / 32;
var offset = bit % 32;
var incoming = (dividend.limbs[word] >> offset as u32) & 1;
if incoming != 0 {
if r.len == 0 { r.len = 1; r.limbs[0] = 0; }
r.limbs[0] = r.limbs[0] | 1;
}
if cmp_abs(r, divisor) >= 0 {
sub_abs_in_place(&r, divisor);
q.limbs[word] = q.limbs[word] | ((1 as u32) << offset as u32);
}
}
q.negative = q.len != 0 && dividend.negative != divisor.negative;
r.negative = r.len != 0 && dividend.negative;
normalize(&q);
normalize(&r);
replace(quotient, &q);
replace(remainder, &r);
ret .Ok;
}
fn mul_small(self: *BigInt, value: u32): Status {
if self.len == 0 || value == 1 { ret .Ok; }
if value == 0 { self.len = 0; self.negative = false; ret .Ok; }
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(self, self.len + 1) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var carry: u64 = 0;
var i: usize = 0;
while i < self.len {
var product = (self.limbs[i] as u64) * (value as u64) + carry;
self.limbs[i] = product as u32;
carry = product >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
ret .Ok;
}
fn add_small(self: *BigInt, value: u32): Status {
if value == 0 { ret .Ok; }
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(self, self.len + 1) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var carry = value as u64;
var i: usize = 0;
while carry != 0 && i < self.len {
var sum = (self.limbs[i] as u64) + carry;
self.limbs[i] = sum as u32;
carry = sum >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
ret .Ok;
}
fn parse(out: *BigInt, bytes: []u8, radix: u8): Status {
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
if bytes.len == 0 { ret .Err(Error.at(.UnexpectedEnd, 0)); }
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
var i: usize = 0;
var negative = false;
if bytes.ptr[0] == '-' || bytes.ptr[0] == '+' {
negative = bytes.ptr[0] == '-';
i = 1;
}
if i == bytes.len { ret .Err(Error.at(.UnexpectedEnd, i)); }
var fresh = zero();
while i < bytes.len {
var digit: u8 = 0;
match digit_value(bytes.ptr[i], radix) {
.Ok(value) => { digit = value; },
.Err(error) => { drop(&fresh); ret .Err(Error.at(.InvalidDigit, i)); },
}
match mul_small(&fresh, radix as u32) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
match add_small(&fresh, digit as u32) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
i = i + 1;
}
fresh.negative = fresh.len != 0 && negative;
replace(out, &fresh);
ret .Ok;
}
fn div_small(self: *BigInt, radix: u32): u32 {
var remainder: u64 = 0;
var i = self.len;
while i > 0 {
i = i - 1;
var current = (remainder << 32) | (self.limbs[i] as u64);
self.limbs[i] = (current / radix as u64) as u32;
remainder = current % radix as u64;
}
normalize(self);
ret remainder as u32;
}
fn format(out: *ByteBuffer, source: BigInt, radix: u8, uppercase: bool): Status {
if !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
var reverse = ByteBuffer.new();
var fresh = ByteBuffer.new();
var magnitude = zero();
match clone(&magnitude, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
magnitude.negative = false;
if magnitude.len == 0 {
match ByteBuffer.append_byte(&reverse, '0') {
.Ok => {}, .Err(error) => { drop(&magnitude); ret .Err(error); },
}
}
while magnitude.len != 0 {
var remainder = div_small(&magnitude, radix as u32);
var byte: u8 = 0;
match digit_char(remainder as u8, uppercase) {
.Ok(value) => { byte = value; },
.Err(error) => { drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error); },
}
match ByteBuffer.append_byte(&reverse, byte) {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
},
}
}
if source.negative {
match ByteBuffer.append_byte(&fresh, '-') {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
},
}
}
var i = ByteBuffer.size(reverse);
var digits = ByteBuffer.as_slice(reverse);
while i > 0 {
i = i - 1;
match ByteBuffer.append_byte(&fresh, digits.ptr[i]) {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ByteBuffer.drop(&fresh);
ret .Err(error);
},
}
}
drop(&magnitude);
ByteBuffer.drop(&reverse);
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn to_u64(out: *u64, source: BigInt): Status {
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.negative || source.len > 2 { ret .Err(Error.make(.Overflow)); }
var value: u64 = 0;
if source.len > 0 { value = source.limbs[0] as u64; }
if source.len > 1 { value = value | ((source.limbs[1] as u64) << 32); }
out.* = value;
ret .Ok;
}
fn to_i64(out: *i64, source: BigInt): Status {
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.len > 2 { ret .Err(Error.make(.Overflow)); }
var magnitude: u64 = 0;
if source.len > 0 { magnitude = source.limbs[0] as u64; }
if source.len > 1 { magnitude = magnitude | ((source.limbs[1] as u64) << 32); }
var max_positive: u64 = 9223372036854775807; // i64::MAX
var min_magnitude: u64 = 9223372036854775808; // |i64::MIN|
if !source.negative {
if magnitude > max_positive { ret .Err(Error.make(.Overflow)); }
out.* = magnitude as i64;
ret .Ok;
}
if magnitude > min_magnitude { ret .Err(Error.make(.Overflow)); }
if magnitude == min_magnitude {
out.* = magnitude as i64;
} else {
out.* = 0 - magnitude as i64;
}
ret .Ok;
}
fn to_usize(out: *usize, source: BigInt): Status {
if out == null { ret .Err(Error.make(.InvalidArgument)); }
var value: u64 = 0;
match to_u64(&value, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if value > (~(0 as usize)) as u64 { ret .Err(Error.make(.Overflow)); }
out.* = value as usize;
ret .Ok;
}
// 任意正位宽的位串。value 始终按无符号值规范化,符号解释由具体操作显式指定。
type Bits = struct {
value: BigInt,
bit_width: usize,
type Result = enum {
Ok: Bits,
Err: Error,
}
type Endian = enum {
Little,
Big,
}
fn limb_count(width: usize): usize {
var count = width / 32;
if width % 32 != 0 { count = count + 1; }
ret count;
}
fn blank(): Bits {
ret Bits { .value = BigInt.zero(), .bit_width = 0 };
}
fn new(width: usize): Bits.Result {
if width == 0 { ret .Err(Error.make(.InvalidArgument)); }
ret .Ok(Bits { .value = BigInt.zero(), .bit_width = width });
}
fn valid(self: Bits): bool {
if self.bit_width == 0 || !BigInt.valid(self.value) || self.value.negative {
ret false;
}
var bits: usize = 0;
match BigInt.bit_length(&bits, self.value) {
.Ok => {},
.Err(error) => { ret false; },
}
ret bits <= self.bit_width;
}
fn drop(self: *Bits) {
BigInt.drop(&self.value);
self.bit_width = 0;
}
fn take(source: *Bits): Bits {
var result = source.*;
source.* = Bits.blank();
ret result;
}
fn replace(out: *Bits, fresh: *Bits) {
Bits.drop(out);
out.* = Bits.take(fresh);
}
fn clone(out: *Bits, source: Bits): Status {
if !Bits.valid(out.*) || !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var fresh_r = Bits.new(source.bit_width);
var fresh = Bits.blank();
match fresh_r {
.Ok(value) => { fresh = value; },
.Err(error) => { ret .Err(error); },
}
match BigInt.clone(&fresh.value, source.value) {
.Ok => {},
.Err(error) => { Bits.drop(&fresh); ret .Err(error); },
}
Bits.replace(out, &fresh);
ret .Ok;
}
fn same_width(a: Bits, b: Bits): bool {
ret Bits.valid(a) && Bits.valid(b) && a.bit_width == b.bit_width;
}
fn sign_bit(self: Bits): bool {
var bit = self.bit_width - 1;
var word = bit / 32;
var offset = bit % 32;
if word >= self.value.len { ret false; }
ret ((self.value.limbs[word] >> offset as u32) & 1) != 0;
}
fn mask_top(self: *Bits) {
var excess = self.bit_width % 32;
if excess != 0 && self.value.len != 0 {
var top = Bits.limb_count(self.bit_width) - 1;
if top < self.value.len {
var mask = ((1 as u32) << excess as u32) - 1;
self.value.limbs[top] = self.value.limbs[top] & mask;
}
}
if self.value.len > Bits.limb_count(self.bit_width) {
self.value.len = Bits.limb_count(self.bit_width);
}
BigInt.normalize(&self.value);
self.value.negative = false;
}
// 显式模 2^width 转换,接受任意正负 BigInt。
fn from_big_truncate(out: *Bits, source: BigInt, width: usize): Status {
if !Bits.valid(out.*) || !BigInt.valid(source) || width == 0 {
ret .Err(Error.make(.InvalidArgument));
}
var fresh_r = Bits.new(width);
var fresh = Bits.blank();
match fresh_r {
.Ok(value) => { fresh = value; },
.Err(error) => { ret .Err(error); },
}
var count = Bits.limb_count(width);
match BigInt.reserve(&fresh.value, count) {
.Ok => {},
.Err(error) => { Bits.drop(&fresh); ret .Err(error); },
}
var i: usize = 0;
while i < count {
var limb: u32 = 0;
if i < source.len { limb = source.limbs[i]; }
if source.negative { limb = ~limb; }
fresh.value.limbs[i] = limb;
i = i + 1;
}
fresh.value.len = count;
if source.negative {
var carry: u64 = 1;
i = 0;
while i < count {
var sum = (fresh.value.limbs[i] as u64) + carry;
fresh.value.limbs[i] = sum as u32;
carry = sum >> 32;
i = i + 1;
}
}
Bits.mask_top(&fresh);
Bits.replace(out, &fresh);
ret .Ok;
}
fn fits_big(source: BigInt, width: usize, signed: bool): bool {
if width == 0 || !BigInt.valid(source) { ret false; }
var bits: usize = 0;
match BigInt.bit_length(&bits, source) {
.Ok => {}, .Err(error) => { ret false; },
}
if !signed { ret !source.negative && bits <= width; }
if !source.negative { ret bits < width; }
if bits < width { ret true; }
if bits > width { ret false; }
var power = BigInt.zero();
var one = BigInt.zero();
defer { BigInt.drop(&power); }
defer { BigInt.drop(&one); }
match BigInt.from_u64(&one, 1) { .Ok => {}, .Err(error) => { ret false; }, }
match BigInt.mul_pow2(&power, one, width - 1) {
.Ok => {}, .Err(error) => { ret false; },
}
ret BigInt.cmp_abs(source, power) == 0;
}
fn from_big_checked(out: *Bits, source: BigInt, width: usize, signed: bool): Status {
if !Bits.valid(out.*) || !BigInt.valid(source) || width == 0 {
ret .Err(Error.make(.InvalidArgument));
}
if !Bits.fits_big(source, width, signed) { ret .Err(Error.make(.Overflow)); }
ret Bits.from_big_truncate(out, source, width);
}
fn to_big(out: *BigInt, source: Bits, signed: bool): Status {
if !BigInt.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
if !signed || !Bits.sign_bit(source) { ret BigInt.clone(out, source.value); }
var modulus = BigInt.zero();
var one = BigInt.zero();
defer { BigInt.drop(&modulus); }
defer { BigInt.drop(&one); }
match BigInt.from_u64(&one, 1) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
match BigInt.mul_pow2(&modulus, one, source.bit_width) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
ret BigInt.sub(out, source.value, modulus);
}
fn cmp_unsigned(out: *i32, a: Bits, b: Bits): Status {
if out == null { ret .Err(Error.make(.InvalidArgument)); }
if !Bits.same_width(a, b) { ret .Err(Error.make(.WidthMismatch)); }
out.* = BigInt.cmp_abs(a.value, b.value);
ret .Ok;
}
fn cmp_signed(out: *i32, a: Bits, b: Bits): Status {
if out == null { ret .Err(Error.make(.InvalidArgument)); }
if !Bits.same_width(a, b) { ret .Err(Error.make(.WidthMismatch)); }
var an = Bits.sign_bit(a);
var bn = Bits.sign_bit(b);
if an != bn {
if an { out.* = -1; } else { out.* = 1; }
} else {
out.* = BigInt.cmp_abs(a.value, b.value);
}
ret .Ok;
}
fn bitwise(out: *Bits, a: Bits, b: Bits, operation: u8): Status {
if !Bits.valid(out.*) || !Bits.same_width(a, b) || out.bit_width != a.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
var fresh_r = Bits.new(a.bit_width);
var fresh = Bits.blank();
match fresh_r { .Ok(value) => { fresh = value; }, .Err(error) => { ret .Err(error); }, }
var count = Bits.limb_count(a.bit_width);
match BigInt.reserve(&fresh.value, count) {
.Ok => {}, .Err(error) => { Bits.drop(&fresh); ret .Err(error); },
}
var i: usize = 0;
while i < count {
var av: u32 = 0;
var bv: u32 = 0;
if i < a.value.len { av = a.value.limbs[i]; }
if i < b.value.len { bv = b.value.limbs[i]; }
if operation == 0 { fresh.value.limbs[i] = av & bv; }
else if operation == 1 { fresh.value.limbs[i] = av | bv; }
else { fresh.value.limbs[i] = av ^ bv; }
i = i + 1;
}
fresh.value.len = count;
Bits.mask_top(&fresh);
Bits.replace(out, &fresh);
ret .Ok;
}
fn bit_and(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 0); }
fn bit_or(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 1); }
fn bit_xor(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 2); }
fn bit_not(out: *Bits, source: Bits): Status {
if !Bits.valid(out.*) || !Bits.valid(source) || out.bit_width != source.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
var fresh_r = Bits.new(source.bit_width);
var fresh = Bits.blank();
match fresh_r {
.Ok(value) => { fresh = value; }, .Err(error) => { ret .Err(error); },
}
var count = Bits.limb_count(source.bit_width);
match BigInt.reserve(&fresh.value, count) {
.Ok => {}, .Err(error) => { Bits.drop(&fresh); ret .Err(error); },
}
var i: usize = 0;
while i < count {
var limb: u32 = 0;
if i < source.value.len { limb = source.value.limbs[i]; }
fresh.value.limbs[i] = ~limb;
i = i + 1;
}
fresh.value.len = count;
Bits.mask_top(&fresh);
Bits.replace(out, &fresh);
ret .Ok;
}
fn shift_left(out: *Bits, source: Bits, amount: usize): Status {
if !Bits.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
if out.bit_width != source.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
if amount >= source.bit_width { ret .Err(Error.make(.OutOfRange)); }
var shifted = BigInt.zero();
defer { BigInt.drop(&shifted); }
match BigInt.mul_pow2(&shifted, source.value, amount) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
ret Bits.from_big_truncate(out, shifted, source.bit_width);
}
fn shift_right(out: *Bits, source: Bits, amount: usize): Status {
if !Bits.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
if out.bit_width != source.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
if amount >= source.bit_width { ret .Err(Error.make(.OutOfRange)); }
var shifted = BigInt.zero();
defer { BigInt.drop(&shifted); }
match BigInt.div_pow2(&shifted, source.value, amount) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
ret Bits.from_big_truncate(out, shifted, source.bit_width);
}
fn shift_right_arithmetic(out: *Bits, source: Bits, amount: usize): Status {
if !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var negative = Bits.sign_bit(source);
match Bits.shift_right(out, source, amount) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if !negative || amount == 0 { ret .Ok; }
var bit = source.bit_width - amount;
while bit < source.bit_width {
var word = bit / 32;
var offset = bit % 32;
match BigInt.reserve(&out.value, word + 1) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
while out.value.len <= word {
out.value.limbs[out.value.len] = 0;
out.value.len = out.value.len + 1;
}
out.value.limbs[word] = out.value.limbs[word] | ((1 as u32) << offset as u32);
bit = bit + 1;
}
Bits.mask_top(out);
ret .Ok;
}
fn arithmetic(out: *Bits, a: Bits, b: Bits, signed: bool, operation: u8,
wrapping: bool): Status {
if !Bits.valid(out.*) || !Bits.same_width(a, b) || out.bit_width != a.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
var av = BigInt.zero();
var bv = BigInt.zero();
var result = BigInt.zero();
defer { BigInt.drop(&result); }
defer { BigInt.drop(&bv); }
defer { BigInt.drop(&av); }
match Bits.to_big(&av, a, signed) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
match Bits.to_big(&bv, b, signed) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
if operation == 0 {
match BigInt.add(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
} else if operation == 1 {
match BigInt.sub(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
} else {
match BigInt.mul(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, }
}
if wrapping { ret Bits.from_big_truncate(out, result, a.bit_width); }
ret Bits.from_big_checked(out, result, a.bit_width, signed);
}
fn add_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status {
ret Bits.arithmetic(out, a, b, signed, 0, false);
}
fn sub_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status {
ret Bits.arithmetic(out, a, b, signed, 1, false);
}
fn mul_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status {
ret Bits.arithmetic(out, a, b, signed, 2, false);
}
fn add_wrapping(out: *Bits, a: Bits, b: Bits): Status {
ret Bits.arithmetic(out, a, b, false, 0, true);
}
fn sub_wrapping(out: *Bits, a: Bits, b: Bits): Status {
ret Bits.arithmetic(out, a, b, false, 1, true);
}
fn mul_wrapping(out: *Bits, a: Bits, b: Bits): Status {
ret Bits.arithmetic(out, a, b, false, 2, true);
}
fn truncate(out: *Bits, source: Bits, width: usize): Status {
if width == 0 || width > source.bit_width { ret .Err(Error.make(.InvalidArgument)); }
if !Bits.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
ret Bits.from_big_truncate(out, source.value, width);
}
fn zero_extend(out: *Bits, source: Bits, width: usize): Status {
if width < source.bit_width { ret .Err(Error.make(.InvalidArgument)); }
if !Bits.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
ret Bits.from_big_checked(out, source.value, width, false);
}
fn sign_extend(out: *Bits, source: Bits, width: usize): Status {
if width < source.bit_width { ret .Err(Error.make(.InvalidArgument)); }
if !Bits.valid(out.*) || !Bits.valid(source) {
ret .Err(Error.make(.InvalidArgument));
}
var signed_value = BigInt.zero();
defer { BigInt.drop(&signed_value); }
match Bits.to_big(&signed_value, source, true) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
ret Bits.from_big_truncate(out, signed_value, width);
}
fn div_rem(quotient: *Bits, remainder: *Bits, dividend: Bits, divisor: Bits,
signed: bool): Status {
if (quotient as usize) == (remainder as usize) {
ret .Err(Error.make(.InvalidArgument));
}
if !Bits.valid(quotient.*) || !Bits.valid(remainder.*) ||
!Bits.same_width(dividend, divisor) ||
quotient.bit_width != dividend.bit_width ||
remainder.bit_width != dividend.bit_width {
ret .Err(Error.make(.WidthMismatch));
}
var a = BigInt.zero();
var b = BigInt.zero();
var q = BigInt.zero();
var r = BigInt.zero();
defer { BigInt.drop(&r); }
defer { BigInt.drop(&q); }
defer { BigInt.drop(&b); }
defer { BigInt.drop(&a); }
match Bits.to_big(&a, dividend, signed) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
match Bits.to_big(&b, divisor, signed) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
match BigInt.div_rem(&q, &r, a, b) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var q_r = Bits.new(dividend.bit_width);
var r_r = Bits.new(dividend.bit_width);
var q_bits = Bits.blank();
var r_bits = Bits.blank();
match q_r { .Ok(value) => { q_bits = value; }, .Err(error) => { ret .Err(error); }, }
match r_r {
.Ok(value) => { r_bits = value; },
.Err(error) => { Bits.drop(&q_bits); ret .Err(error); },
}
match Bits.from_big_checked(&q_bits, q, dividend.bit_width, signed) {
.Ok => {},
.Err(error) => { Bits.drop(&r_bits); Bits.drop(&q_bits); ret .Err(error); },
}
match Bits.from_big_checked(&r_bits, r, dividend.bit_width, signed) {
.Ok => {},
.Err(error) => { Bits.drop(&r_bits); Bits.drop(&q_bits); ret .Err(error); },
}
Bits.replace(quotient, &q_bits);
Bits.replace(remainder, &r_bits);
ret .Ok;
}
fn byte_len(self: Bits): usize {
ret self.bit_width / 8 + (self.bit_width % 8 != 0) as usize;
}
fn little(endian: Bits.Endian): bool {
match endian { .Little => { ret true; }, .Big => { ret false; }, }
}
fn import_bytes(out: *Bits, bytes: []u8, width: usize, endian: Bits.Endian): Status {
if !Bits.valid(out.*) || width == 0 { ret .Err(Error.make(.InvalidArgument)); }
var expected = width / 8 + (width % 8 != 0) as usize;
if bytes.len != expected { ret .Err(Error.make(.OutOfRange)); }
if bytes.len != 0 && bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
var value = BigInt.zero();
defer { BigInt.drop(&value); }
var i: usize = 0;
while i < bytes.len {
var index = i;
if Bits.little(endian) { index = bytes.len - 1 - i; }
match BigInt.mul_small(&value, 256) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
match BigInt.add_small(&value, bytes.ptr[index] as u32) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
i = i + 1;
}
ret Bits.from_big_checked(out, value, width, false);
}
fn export_bytes(out: *ByteBuffer, source: Bits, endian: Bits.Endian): Status {
if !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var fresh = ByteBuffer.new();
var count = Bits.byte_len(source);
var i: usize = 0;
while i < count {
var logical = i;
if !Bits.little(endian) { logical = count - 1 - i; }
var word = logical / 4;
var offset = (logical % 4) * 8;
var byte: u8 = 0;
if word < source.value.len {
byte = (source.value.limbs[word] >> offset as u32) as u8;
}
match ByteBuffer.append_byte(&fresh, byte) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
i = i + 1;
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
}
fn expect_ok(result: Status) {
match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected BigInt error"); }, }
}
fn expect_text_radix(value: BigInt, radix: u8, uppercase: bool, expected: []u8) {
var buffer = ByteBuffer.new();
expect_ok(format(&buffer, value, radix, uppercase));
var actual = ByteBuffer.as_slice(buffer);
@assert(actual.len == expected.len, "formatted length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
i = i + 1;
}
ByteBuffer.drop(&buffer);
}
fn expect_text(value: BigInt, expected: []u8) {
expect_text_radix(value, 10, false, expected);
}
fn parsed(text: []u8): BigInt {
var value = zero();
expect_ok(parse(&value, text, 10));
ret value;
}
fn test_arithmetic() {
var a = parsed("4294967295");
defer { drop(&a); }
var one = zero();
defer { drop(&one); }
expect_ok(from_u64(&one, 1));
expect_ok(add(&a, a, one));
expect_text(a, "4294967296");
var all_ones = zero();
defer { drop(&all_ones); }
expect_ok(parse(&all_ones, "ffffffffffffffffffffffffffffffff", 16));
expect_ok(add(&all_ones, all_ones, one));
expect_text(all_ones, "340282366920938463463374607431768211456");
expect_ok(sub(&all_ones, all_ones, one));
expect_text_radix(all_ones, 16, false, "ffffffffffffffffffffffffffffffff");
var two_64 = parsed("18446744073709551616");
defer { drop(&two_64); }
var product = zero();
defer { drop(&product); }
expect_ok(mul(&product, two_64, two_64));
expect_text(product, "340282366920938463463374607431768211456");
var difference = zero();
defer { drop(&difference); }
expect_ok(sub(&difference, product, two_64));
expect_text(difference, "340282366920938463444927863358058659840");
var shifted = zero();
defer { drop(&shifted); }
expect_ok(mul_pow2(&shifted, one, 257));
var bits: usize = 0;
expect_ok(bit_length(&bits, shifted));
@assert(bits == 258);
expect_ok(div_pow2(&shifted, shifted, 257));
@assert(eq(shifted, one));
}
fn test_division_and_sign() {
var dividend = parsed("340282366920938463463374607431768211456");
defer { drop(&dividend); }
var divisor = parsed("18446744073709551616");
defer { drop(&divisor); }
var quotient = zero();
defer { drop(&quotient); }
var remainder = zero();
defer { drop(&remainder); }
expect_ok(div_rem(&quotient, &remainder, dividend, divisor));
@assert(eq(quotient, divisor));
@assert(is_zero(remainder));
var long_dividend = parsed("123456789012345678901234567890");
defer { drop(&long_dividend); }
var long_divisor = parsed("9876543210");
defer { drop(&long_divisor); }
expect_ok(div_rem(&quotient, &remainder, long_dividend, long_divisor));
expect_text(quotient, "12499999887343749990");
expect_text(remainder, "1562499990");
var negative = zero();
defer { drop(&negative); }
var seven = zero();
defer { drop(&seven); }
expect_ok(from_i64(&negative, -100));
expect_ok(from_u64(&seven, 7));
expect_ok(div_rem(&negative, &remainder, negative, seven));
expect_text(negative, "-14");
expect_text(remainder, "-2");
var reconstructed = zero();
defer { drop(&reconstructed); }
expect_ok(mul(&reconstructed, negative, seven));
expect_ok(add(&reconstructed, reconstructed, remainder));
expect_text(reconstructed, "-100");
var saved = parsed("123");
defer { drop(&saved); }
var zero_divisor = zero();
defer { drop(&zero_divisor); }
match div_rem(&saved, &remainder, dividend, zero_divisor) {
.Ok => { @assert(false, "division by zero"); },
.Err(error) => { match error.kind { .DivisionByZero => {}, _ => { @assert(false); }, } },
}
expect_text(saved, "123");
}
fn test_parse_format_and_conversion() {
var value = zero();
defer { drop(&value); }
expect_ok(parse(&value, "-deadBEEF", 16));
var output = ByteBuffer.new();
defer { ByteBuffer.drop(&output); }
expect_ok(format(&output, value, 16, false));
var text = ByteBuffer.as_slice(output);
@assert(text.len == 9 && text.ptr[0] == '-' && text.ptr[1] == 'd' && text.ptr[8] == 'f');
expect_text_radix(value, 16, true, "-DEADBEEF");
var base36 = zero();
defer { drop(&base36); }
expect_ok(parse(&base36, "zzzzzzzzzz", 36));
expect_text_radix(base36, 36, false, "zzzzzzzzzz");
var preserved = parsed("77");
defer { drop(&preserved); }
match parse(&preserved, "12x", 10) {
.Ok => { @assert(false, "invalid digit"); },
.Err(error) => {
@assert(error.has_position && error.position == 2);
match error.kind { .InvalidDigit => {}, _ => { @assert(false); }, }
},
}
expect_text(preserved, "77");
var max = parsed("18446744073709551615");
defer { drop(&max); }
var converted: u64 = 0;
expect_ok(to_u64(&converted, max));
@assert(converted == 18446744073709551615);
var over = parsed("18446744073709551616");
defer { drop(&over); }
converted = 17;
match to_u64(&converted, over) {
.Ok => { @assert(false); },
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
}
@assert(converted == 17, "failed conversion preserves output");
var minimum = parsed("-9223372036854775808");
defer { drop(&minimum); }
var signed_value: i64 = 0;
expect_ok(to_i64(&signed_value, minimum));
@assert(signed_value == (9223372036854775808 as u64) as i64);
var signed_over = parsed("9223372036854775808");
defer { drop(&signed_over); }
signed_value = 23;
match to_i64(&signed_value, signed_over) {
.Ok => { @assert(false); },
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
}
@assert(signed_value == 23, "failed signed conversion preserves output");
var copied = zero();
defer { drop(&copied); }
expect_ok(clone(&copied, max));
var one_more = parsed("1");
defer { drop(&one_more); }
expect_ok(add(&max, max, one_more));
expect_text(copied, "18446744073709551615");
var moved = take(&copied);
defer { drop(&moved); }
@assert(copied.limbs == null && copied.len == 0 && copied.cap == 0);
expect_text(moved, "18446744073709551615");
}
fn expect_i64_value(value: BigInt, expected: i64) {
var actual: i64 = 0;
expect_ok(to_i64(&actual, value));
@assert(actual == expected, "small integer reference result");
}
fn test_small_reference() {
var left = zero();
defer { drop(&left); }
var right = zero();
defer { drop(&right); }
var result = zero();
defer { drop(&result); }
var remainder = zero();
defer { drop(&remainder); }
var a: i64 = -32;
while a <= 32 {
var b: i64 = -32;
while b <= 32 {
expect_ok(from_i64(&left, a));
expect_ok(from_i64(&right, b));
expect_ok(add(&result, left, right));
expect_i64_value(result, a + b);
expect_ok(sub(&result, left, right));
expect_i64_value(result, a - b);
expect_ok(mul(&result, left, right));
expect_i64_value(result, a * b);
if b != 0 {
expect_ok(div_rem(&result, &remainder, left, right));
expect_i64_value(result, a / b);
expect_i64_value(remainder, a % b);
}
b = b + 1;
}
a = a + 1;
}
}
fn bits_new(width: usize): Bits {
match Bits.new(width) {
.Ok(value) => { ret value; },
.Err(error) => { @assert(false, "Bits construction"); },
}
ret Bits.blank();
}
fn bits_i64(value: i64, width: usize, signed: bool): Bits {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
expect_ok(BigInt.from_i64(&integer, value));
var result = bits_new(width);
expect_ok(Bits.from_big_checked(&result, integer, width, signed));
ret result;
}
fn bits_u64(value: u64, width: usize): Bits {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
expect_ok(BigInt.from_u64(&integer, value));
var result = bits_new(width);
expect_ok(Bits.from_big_checked(&result, integer, width, false));
ret result;
}
fn bits_as_i64(value: Bits, signed: bool): i64 {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
expect_ok(Bits.to_big(&integer, value, signed));
var result: i64 = 0;
expect_ok(BigInt.to_i64(&result, integer));
ret result;
}
fn expect_overflow_status(result: Status) {
match result {
.Ok => { @assert(false, "expected overflow"); },
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
}
}
fn expect_range_status(result: Status) {
match result {
.Ok => { @assert(false, "expected range error"); },
.Err(error) => { match error.kind { .OutOfRange => {}, _ => { @assert(false); }, } },
}
}
fn expect_width_status(result: Status) {
match result {
.Ok => { @assert(false, "expected width mismatch"); },
.Err(error) => { match error.kind { .WidthMismatch => {}, _ => { @assert(false); }, } },
}
}
fn test_bits_widths_and_conversion() {
match Bits.new(0) {
.Ok(value) => { @assert(false, "zero width"); },
.Err(error) => { match error.kind { .InvalidArgument => {}, _ => { @assert(false); }, } },
}
var widths: [6]usize = .[1, 7, 24, 65, 128, 257];
var i: usize = 0;
while i < 6 {
var value = bits_new(widths[i]);
@assert(Bits.valid(value) && value.bit_width == widths[i]);
Bits.drop(&value);
i = i + 1;
}
var minus_one = bits_i64(-1, 7, true);
defer { Bits.drop(&minus_one); }
@assert(bits_as_i64(minus_one, true) == -1);
@assert(bits_as_i64(minus_one, false) == 127);
var one_bit = bits_u64(1, 1);
defer { Bits.drop(&one_bit); }
@assert(bits_as_i64(one_bit, true) == -1 && bits_as_i64(one_bit, false) == 1);
var all_128_int = parsed("340282366920938463463374607431768211455");
defer { BigInt.drop(&all_128_int); }
var all_128 = bits_new(128);
defer { Bits.drop(&all_128); }
expect_ok(Bits.from_big_checked(&all_128, all_128_int, 128, false));
@assert(all_128.value.len == 4 && all_128.value.limbs[3] == 0xffffffff);
var all_257 = bits_i64(-1, 257, true);
defer { Bits.drop(&all_257); }
@assert(all_257.value.len == 9 && all_257.value.limbs[8] == 1,
"257-bit top mask");
@assert(bits_as_i64(all_257, true) == -1);
var wide_text = parsed("36893488147419103231");
defer { BigInt.drop(&wide_text); }
var wide = bits_new(65);
defer { Bits.drop(&wide); }
expect_ok(Bits.from_big_checked(&wide, wide_text, 65, false));
var bit_count: usize = 0;
expect_ok(BigInt.bit_length(&bit_count, wide.value));
@assert(bit_count == 65 && wide.value.limbs[2] == 1, "65-bit top mask");
}
fn test_bits_arithmetic() {
var one = bits_u64(1, 7);
defer { Bits.drop(&one); }
var max = bits_u64(127, 7);
defer { Bits.drop(&max); }
var out = bits_u64(5, 7);
defer { Bits.drop(&out); }
expect_overflow_status(Bits.add_checked(&out, max, one, false));
@assert(bits_as_i64(out, false) == 5, "overflow preserves output");
expect_ok(Bits.add_wrapping(&out, max, one));
@assert(bits_as_i64(out, false) == 0, "u7 wrapping add");
var signed_max = bits_i64(63, 7, true);
defer { Bits.drop(&signed_max); }
expect_overflow_status(Bits.add_checked(&out, signed_max, one, true));
var signed_min = bits_i64(-64, 7, true);
defer { Bits.drop(&signed_min); }
expect_overflow_status(Bits.sub_checked(&out, signed_min, one, true));
var twenty = bits_u64(20, 7);
defer { Bits.drop(&twenty); }
var ten = bits_u64(10, 7);
defer { Bits.drop(&ten); }
expect_ok(Bits.mul_wrapping(&out, twenty, ten));
@assert(bits_as_i64(out, false) == 72, "u7 wrapping multiply");
}
fn test_bits_logic_and_shift() {
var a = bits_u64(85, 7);
defer { Bits.drop(&a); }
var b = bits_u64(15, 7);
defer { Bits.drop(&b); }
var out = bits_new(7);
defer { Bits.drop(&out); }
expect_ok(Bits.bit_and(&out, a, b));
@assert(bits_as_i64(out, false) == 5);
expect_ok(Bits.bit_or(&out, a, b));
@assert(bits_as_i64(out, false) == 95);
expect_ok(Bits.bit_xor(&out, a, b));
@assert(bits_as_i64(out, false) == 90);
expect_ok(Bits.bit_not(&out, a));
@assert(bits_as_i64(out, false) == 42);
var signed = bits_u64(65, 7);
defer { Bits.drop(&signed); }
expect_ok(Bits.shift_left(&out, signed, 1));
@assert(bits_as_i64(out, false) == 2);
expect_ok(Bits.shift_right(&out, signed, 1));
@assert(bits_as_i64(out, false) == 32);
expect_ok(Bits.shift_right_arithmetic(&out, signed, 1));
@assert(bits_as_i64(out, false) == 96);
expect_range_status(Bits.shift_left(&out, signed, 7));
@assert(bits_as_i64(out, false) == 96, "bad shift preserves output");
var order: i32 = 0;
expect_ok(Bits.cmp_unsigned(&order, signed, a));
@assert(order < 0);
expect_ok(Bits.cmp_signed(&order, signed, a));
@assert(order < 0);
var other_width = bits_u64(1, 24);
defer { Bits.drop(&other_width); }
order = 17;
expect_width_status(Bits.cmp_unsigned(&order, a, other_width));
@assert(order == 17, "width mismatch preserves comparison output");
expect_width_status(Bits.bit_and(&out, a, other_width));
expect_ok(Bits.shift_right_arithmetic(&signed, signed, 1));
@assert(bits_as_i64(signed, false) == 96, "aliased arithmetic shift");
}
fn test_bits_resize_division_and_bytes() {
var source = bits_u64(85, 7);
defer { Bits.drop(&source); }
var resized = bits_new(7);
defer { Bits.drop(&resized); }
expect_ok(Bits.truncate(&resized, source, 4));
@assert(bits_as_i64(resized, false) == 5);
expect_ok(Bits.zero_extend(&resized, source, 24));
@assert(bits_as_i64(resized, false) == 85 && resized.bit_width == 24);
var negative = bits_u64(65, 7);
defer { Bits.drop(&negative); }
expect_ok(Bits.sign_extend(&resized, negative, 24));
@assert(bits_as_i64(resized, true) == -63);
var hundred = bits_u64(100, 8);
defer { Bits.drop(&hundred); }
var seven = bits_u64(7, 8);
defer { Bits.drop(&seven); }
var quotient = bits_new(8);
defer { Bits.drop(&quotient); }
var remainder = bits_new(8);
defer { Bits.drop(&remainder); }
expect_ok(Bits.div_rem(&quotient, &remainder, hundred, seven, false));
@assert(bits_as_i64(quotient, false) == 14 && bits_as_i64(remainder, false) == 2);
var neg_hundred = bits_i64(-100, 8, true);
defer { Bits.drop(&neg_hundred); }
expect_ok(Bits.div_rem(&quotient, &remainder, neg_hundred, seven, true));
@assert(bits_as_i64(quotient, true) == -14 && bits_as_i64(remainder, true) == -2);
var min = bits_i64(-128, 8, true);
defer { Bits.drop(&min); }
var minus_one = bits_i64(-1, 8, true);
defer { Bits.drop(&minus_one); }
expect_overflow_status(Bits.div_rem(&quotient, &remainder, min, minus_one, true));
var bytes: [3]u8 = .[0x12, 0x34, 0x56];
expect_ok(Bits.import_bytes(&resized, bytes[0..3], 24, .Big));
@assert(bits_as_i64(resized, false) == 0x123456);
var output = ByteBuffer.new();
defer { ByteBuffer.drop(&output); }
expect_ok(Bits.export_bytes(&output, resized, .Little));
var exported = ByteBuffer.as_slice(output);
@assert(exported.len == 3 && exported.ptr[0] == 0x56 &&
exported.ptr[1] == 0x34 && exported.ptr[2] == 0x12);
var bad: [1]u8 = .[0xff];
var seven_bits = bits_u64(3, 7);
defer { Bits.drop(&seven_bits); }
expect_overflow_status(Bits.import_bytes(&seven_bits, bad[0..1], 7, .Big));
@assert(bits_as_i64(seven_bits, false) == 3, "bad import preserves output");
var copied = bits_new(7);
defer { Bits.drop(&copied); }
expect_ok(Bits.clone(&copied, seven_bits));
expect_ok(Bits.bit_not(&seven_bits, seven_bits));
@assert(bits_as_i64(copied, false) == 3, "Bits clone is deep");
var moved = Bits.take(&copied);
defer { Bits.drop(&moved); }
@assert(copied.bit_width == 0 && copied.value.limbs == null, "Bits take empties source");
@assert(bits_as_i64(moved, false) == 3);
}
fn main() {
test_arithmetic();
test_division_and_sign();
test_parse_format_and_conversion();
test_small_reference();
test_bits_widths_and_conversion();
test_bits_arithmetic();
test_bits_logic_and_shift();
test_bits_resize_division_and_bytes();
}