// 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(÷nd); } var divisor = parsed("18446744073709551616"); defer { drop(&divisor); } var quotient = zero(); defer { drop("ient); } var remainder = zero(); defer { drop(&remainder); } expect_ok(div_rem("ient, &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("ient, &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("ient); } var remainder = bits_new(8); defer { Bits.drop(&remainder); } expect_ok(Bits.div_rem("ient, &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("ient, &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("ient, &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(); }