struct Const [src]

A arbitrary-precision big integer, with a fixed set of immutable limbs.

Fields

limbs: []const LimbRaw digits. These are: Little-endian ordered limbs.len >= 1 Zero is represented as limbs.len == 1 with limbs[0] == 0. Accessing limbs directly should be avoided.
positive: bool

Members

Source

pub const Const = struct { /// Raw digits. These are: /// /// * Little-endian ordered /// * limbs.len >= 1 /// * Zero is represented as limbs.len == 1 with limbs[0] == 0. /// /// Accessing limbs directly should be avoided. limbs: []const Limb, positive: bool, /// The result is an independent resource which is managed by the caller. pub fn toManaged(self: Const, allocator: Allocator) Allocator.Error!Managed { const limbs = try allocator.alloc(Limb, @max(Managed.default_capacity, self.limbs.len)); @memcpy(limbs[0..self.limbs.len], self.limbs); return Managed{ .allocator = allocator, .limbs = limbs, .metadata = if (self.positive) self.limbs.len & ~Managed.sign_bit else self.limbs.len | Managed.sign_bit, }; } /// Asserts `limbs` is big enough to store the value. pub fn toMutable(self: Const, limbs: []Limb) Mutable { @memcpy(limbs[0..self.limbs.len], self.limbs[0..self.limbs.len]); return .{ .limbs = limbs, .positive = self.positive, .len = self.limbs.len, }; } pub fn dump(self: Const) void { for (self.limbs[0..self.limbs.len]) |limb| { std.debug.print("{x} ", .{limb}); } std.debug.print("positive={}\n", .{self.positive}); } pub fn abs(self: Const) Const { return .{ .limbs = self.limbs, .positive = true, }; } pub fn negate(self: Const) Const { return .{ .limbs = self.limbs, .positive = !self.positive, }; } pub fn isOdd(self: Const) bool { return self.limbs[0] & 1 != 0; } pub fn isEven(self: Const) bool { return !self.isOdd(); } /// Returns the number of bits required to represent the absolute value of an integer. pub fn bitCountAbs(self: Const) usize { return (self.limbs.len - 1) * limb_bits + (limb_bits - @clz(self.limbs[self.limbs.len - 1])); } /// Returns the number of bits required to represent the integer in twos-complement form. /// /// If the integer is negative the value returned is the number of bits needed by a signed /// integer to represent the value. If positive the value is the number of bits for an /// unsigned integer. Any unsigned integer will fit in the signed integer with bitcount /// one greater than the returned value. /// /// e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7. pub fn bitCountTwosComp(self: Const) usize { var bits = self.bitCountAbs(); // If the entire value has only one bit set (e.g. 0b100000000) then the negation in twos // complement requires one less bit. if (!self.positive) block: { bits += 1; if (@popCount(self.limbs[self.limbs.len - 1]) == 1) { for (self.limbs[0 .. self.limbs.len - 1]) |limb| { if (@popCount(limb) != 0) { break :block; } } bits -= 1; } } return bits; } /// Returns the number of bits required to represent the integer in twos-complement form /// with the given signedness. pub fn bitCountTwosCompForSignedness(self: Const, signedness: std.builtin.Signedness) usize { return self.bitCountTwosComp() + @intFromBool(self.positive and signedness == .signed); } /// @popCount with two's complement semantics. /// /// This returns the number of 1 bits set when the value would be represented in /// two's complement with the given integer width (bit_count). /// This includes the leading sign bit, which will be set for negative values. /// /// Asserts that bit_count is enough to represent value in two's compliment /// and that the final result fits in a usize. /// Asserts that there are no trailing empty limbs on the most significant end, /// i.e. that limb count matches `calcLimbLen()` and zero is not negative. pub fn popCount(self: Const, bit_count: usize) usize { var sum: usize = 0; if (self.positive) { for (self.limbs) |limb| { sum += @popCount(limb); } } else { assert(self.fitsInTwosComp(.signed, bit_count)); assert(self.limbs[self.limbs.len - 1] != 0); var remaining_bits = bit_count; var carry: u1 = 1; var add_res: Limb = undefined; // All but the most significant limb. for (self.limbs[0 .. self.limbs.len - 1]) |limb| { const ov = @addWithOverflow(~limb, carry); add_res = ov[0]; carry = ov[1]; sum += @popCount(add_res); remaining_bits -= limb_bits; // Asserted not to underflow by fitsInTwosComp } // The most significant limb may have fewer than @bitSizeOf(Limb) meaningful bits, // which we can detect with @clz(). // There may also be fewer limbs than needed to fill bit_count. const limb = self.limbs[self.limbs.len - 1]; const leading_zeroes = @clz(limb); // The most significant limb is asserted not to be all 0s (above), // so ~limb cannot be all 1s, and ~limb + 1 cannot overflow. sum += @popCount(~limb + carry); sum -= leading_zeroes; // All leading zeroes were flipped and added to sum, so undo those const remaining_ones = remaining_bits - (limb_bits - leading_zeroes); // All bits not covered by limbs sum += remaining_ones; } return sum; } pub fn fitsInTwosComp(self: Const, signedness: Signedness, bit_count: usize) bool { if (self.eqlZero()) { return true; } if (signedness == .unsigned and !self.positive) { return false; } return bit_count >= self.bitCountTwosCompForSignedness(signedness); } /// Returns whether self can fit into an integer of the requested type. pub fn fits(self: Const, comptime T: type) bool { const info = @typeInfo(T).int; return self.fitsInTwosComp(info.signedness, info.bits); } /// Returns the approximate size of the integer in the given base. Negative values accommodate for /// the minus sign. This is used for determining the number of characters needed to print the /// value. It is inexact and may exceed the given value by ~1-2 bytes. /// TODO See if we can make this exact. pub fn sizeInBaseUpperBound(self: Const, base: usize) usize { const bit_count = @as(usize, @intFromBool(!self.positive)) + self.bitCountAbs(); return (bit_count / math.log2(base)) + 2; } pub const ConvertError = error{ NegativeIntoUnsigned, TargetTooSmall, }; /// Deprecated; use `toInt`. pub const to = toInt; /// Convert self to integer type T. /// /// Returns an error if self cannot be narrowed into the requested type without truncation. pub fn toInt(self: Const, comptime T: type) ConvertError!T { switch (@typeInfo(T)) { .int => |info| { // Make sure -0 is handled correctly. if (self.eqlZero()) return 0; const UT = std.meta.Int(.unsigned, info.bits); if (!self.fitsInTwosComp(info.signedness, info.bits)) { return error.TargetTooSmall; } var r: UT = 0; if (@sizeOf(UT) <= @sizeOf(Limb)) { r = @as(UT, @intCast(self.limbs[0])); } else { for (self.limbs[0..self.limbs.len], 0..) |_, ri| { const limb = self.limbs[self.limbs.len - ri - 1]; r <<= limb_bits; r |= limb; } } if (info.signedness == .unsigned) { return if (self.positive) @as(T, @intCast(r)) else error.NegativeIntoUnsigned; } else { if (self.positive) { return @intCast(r); } else { if (math.cast(T, r)) |ok| { return -ok; } else { return minInt(T); } } } }, else => @compileError("expected int type, found '" ++ @typeName(T) ++ "'"), } } /// Convert self to float type T. pub fn toFloat(self: Const, comptime T: type) T { if (self.limbs.len == 0) return 0; const base = std.math.maxInt(std.math.big.Limb) + 1; var result: f128 = 0; var i: usize = self.limbs.len; while (i != 0) { i -= 1; const limb: f128 = @floatFromInt(self.limbs[i]); result = @mulAdd(f128, base, result, limb); } if (self.positive) { return @floatCast(result); } else { return @floatCast(-result); } } /// To allow `std.fmt.format` to work with this type. /// If the absolute value of integer is greater than or equal to `pow(2, 64 * @sizeOf(usize) * 8)`, /// this function will fail to print the string, printing "(BigInt)" instead of a number. /// This is because the rendering algorithm requires reversing a string, which requires O(N) memory. /// See `toString` and `toStringAlloc` for a way to print big integers without failure. pub fn format( self: Const, comptime fmt: []const u8, options: std.fmt.FormatOptions, out_stream: anytype, ) !void { _ = options; comptime var base = 10; comptime var case: std.fmt.Case = .lower; if (fmt.len == 0 or comptime mem.eql(u8, fmt, "d")) { base = 10; case = .lower; } else if (comptime mem.eql(u8, fmt, "b")) { base = 2; case = .lower; } else if (comptime mem.eql(u8, fmt, "x")) { base = 16; case = .lower; } else if (comptime mem.eql(u8, fmt, "X")) { base = 16; case = .upper; } else { std.fmt.invalidFmtError(fmt, self); } const available_len = 64; if (self.limbs.len > available_len) return out_stream.writeAll("(BigInt)"); var limbs: [calcToStringLimbsBufferLen(available_len, base)]Limb = undefined; const biggest: Const = .{ .limbs = &([1]Limb{comptime math.maxInt(Limb)} ** available_len), .positive = false, }; var buf: [biggest.sizeInBaseUpperBound(base)]u8 = undefined; const len = self.toString(&buf, base, case, &limbs); return out_stream.writeAll(buf[0..len]); } /// Converts self to a string in the requested base. /// Caller owns returned memory. /// Asserts that `base` is in the range [2, 36]. /// See also `toString`, a lower level function than this. pub fn toStringAlloc(self: Const, allocator: Allocator, base: u8, case: std.fmt.Case) Allocator.Error![]u8 { assert(base >= 2); assert(base <= 36); if (self.eqlZero()) { return allocator.dupe(u8, "0"); } const string = try allocator.alloc(u8, self.sizeInBaseUpperBound(base)); errdefer allocator.free(string); const limbs = try allocator.alloc(Limb, calcToStringLimbsBufferLen(self.limbs.len, base)); defer allocator.free(limbs); return allocator.realloc(string, self.toString(string, base, case, limbs)); } /// Converts self to a string in the requested base. /// Asserts that `base` is in the range [2, 36]. /// `string` is a caller-provided slice of at least `sizeInBaseUpperBound` bytes, /// where the result is written to. /// Returns the length of the string. /// `limbs_buffer` is caller-provided memory for `toString` to use as a working area. It must have /// length of at least `calcToStringLimbsBufferLen`. /// In the case of power-of-two base, `limbs_buffer` is ignored. /// See also `toStringAlloc`, a higher level function than this. pub fn toString(self: Const, string: []u8, base: u8, case: std.fmt.Case, limbs_buffer: []Limb) usize { assert(base >= 2); assert(base <= 36); if (self.eqlZero()) { string[0] = '0'; return 1; } var digits_len: usize = 0; // Power of two: can do a single pass and use masks to extract digits. if (math.isPowerOfTwo(base)) { const base_shift = math.log2_int(Limb, base); outer: for (self.limbs[0..self.limbs.len]) |limb| { var shift: usize = 0; while (shift < limb_bits) : (shift += base_shift) { const r = @as(u8, @intCast((limb >> @as(Log2Limb, @intCast(shift))) & @as(Limb, base - 1))); const ch = std.fmt.digitToChar(r, case); string[digits_len] = ch; digits_len += 1; // If we hit the end, it must be all zeroes from here. if (digits_len == string.len) break :outer; } } // Always will have a non-zero digit somewhere. while (string[digits_len - 1] == '0') { digits_len -= 1; } } else { // Non power-of-two: batch divisions per word size. // We use a HalfLimb here so the division uses the faster lldiv0p5 over lldiv1 codepath. const digits_per_limb = math.log(HalfLimb, base, maxInt(HalfLimb)); var limb_base: Limb = 1; var j: usize = 0; while (j < digits_per_limb) : (j += 1) { limb_base *= base; } const b: Const = .{ .limbs = &[_]Limb{limb_base}, .positive = true }; var q: Mutable = .{ .limbs = limbs_buffer[0 .. self.limbs.len + 2], .positive = true, // Make absolute by ignoring self.positive. .len = self.limbs.len, }; @memcpy(q.limbs[0..self.limbs.len], self.limbs); var r: Mutable = .{ .limbs = limbs_buffer[q.limbs.len..][0..self.limbs.len], .positive = true, .len = 1, }; r.limbs[0] = 0; const rest_of_the_limbs_buf = limbs_buffer[q.limbs.len + r.limbs.len ..]; while (q.len >= 2) { // Passing an allocator here would not be helpful since this division is destroying // information, not creating it. [TODO citation needed] q.divTrunc(&r, q.toConst(), b, rest_of_the_limbs_buf); var r_word = r.limbs[0]; var i: usize = 0; while (i < digits_per_limb) : (i += 1) { const ch = std.fmt.digitToChar(@as(u8, @intCast(r_word % base)), case); r_word /= base; string[digits_len] = ch; digits_len += 1; } } { assert(q.len == 1); var r_word = q.limbs[0]; while (r_word != 0) { const ch = std.fmt.digitToChar(@as(u8, @intCast(r_word % base)), case); r_word /= base; string[digits_len] = ch; digits_len += 1; } } } if (!self.positive) { string[digits_len] = '-'; digits_len += 1; } const s = string[0..digits_len]; mem.reverse(u8, s); return s.len; } /// Write the value of `x` into `buffer` /// Asserts that `buffer` is large enough to store the value. /// /// `buffer` is filled so that its contents match what would be observed via /// @ptrCast(*[buffer.len]const u8, &x). Byte ordering is determined by `endian`, /// and any required padding bits are added on the MSB end. pub fn writeTwosComplement(x: Const, buffer: []u8, endian: Endian) void { return writePackedTwosComplement(x, buffer, 0, 8 * buffer.len, endian); } /// Write the value of `x` to a packed memory `buffer`. /// Asserts that `buffer` is large enough to contain a value of bit-size `bit_count` /// at offset `bit_offset`. /// /// This is equivalent to storing the value of an integer with `bit_count` bits as /// if it were a field in packed memory at the provided bit offset. pub fn writePackedTwosComplement(x: Const, buffer: []u8, bit_offset: usize, bit_count: usize, endian: Endian) void { assert(x.fitsInTwosComp(if (x.positive) .unsigned else .signed, bit_count)); // Copy all complete limbs var carry: u1 = 1; var limb_index: usize = 0; var bit_index: usize = 0; while (limb_index < bit_count / @bitSizeOf(Limb)) : (limb_index += 1) { var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; // 2's complement (bitwise not, then add carry bit) if (!x.positive) { const ov = @addWithOverflow(~limb, carry); limb = ov[0]; carry = ov[1]; } // Write one Limb of bits mem.writePackedInt(Limb, buffer, bit_index + bit_offset, limb, endian); bit_index += @bitSizeOf(Limb); } // Copy the remaining bits if (bit_count != bit_index) { var limb: Limb = if (limb_index < x.limbs.len) x.limbs[limb_index] else 0; // 2's complement (bitwise not, then add carry bit) if (!x.positive) limb = ~limb +% carry; // Write all remaining bits mem.writeVarPackedInt(buffer, bit_index + bit_offset, bit_count - bit_index, limb, endian); } } /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if /// `|a| < |b|`, `|a| == |b|`, or `|a| > |b|` respectively. pub fn orderAbs(a: Const, b: Const) math.Order { if (a.limbs.len < b.limbs.len) { return .lt; } if (a.limbs.len > b.limbs.len) { return .gt; } var i: usize = a.limbs.len - 1; while (i != 0) : (i -= 1) { if (a.limbs[i] != b.limbs[i]) { break; } } if (a.limbs[i] < b.limbs[i]) { return .lt; } else if (a.limbs[i] > b.limbs[i]) { return .gt; } else { return .eq; } } /// Returns `math.Order.lt`, `math.Order.eq`, `math.Order.gt` if `a < b`, `a == b` or `a > b` respectively. pub fn order(a: Const, b: Const) math.Order { if (a.positive != b.positive) { if (eqlZero(a) and eqlZero(b)) { return .eq; } else { return if (a.positive) .gt else .lt; } } else { const r = orderAbs(a, b); return if (a.positive) r else switch (r) { .lt => math.Order.gt, .eq => math.Order.eq, .gt => math.Order.lt, }; } } /// Same as `order` but the right-hand operand is a primitive integer. pub fn orderAgainstScalar(lhs: Const, scalar: anytype) math.Order { // Normally we could just determine the number of limbs needed with calcLimbLen, // but that is not comptime-known when scalar is not a comptime_int. Instead, we // use calcTwosCompLimbCount for a non-comptime_int scalar, which can be pessimistic // in the case that scalar happens to be small in magnitude within its type, but it // is well worth being able to use the stack and not needing an allocator passed in. // Note that Mutable.init still sets len to calcLimbLen(scalar) in any case. const limb_len = comptime switch (@typeInfo(@TypeOf(scalar))) { .comptime_int => calcLimbLen(scalar), .int => |info| calcTwosCompLimbCount(info.bits), else => @compileError("expected scalar to be an int"), }; var limbs: [limb_len]Limb = undefined; const rhs = Mutable.init(&limbs, scalar); return order(lhs, rhs.toConst()); } /// Returns true if `a == 0`. pub fn eqlZero(a: Const) bool { var d: Limb = 0; for (a.limbs) |limb| d |= limb; return d == 0; } /// Returns true if `|a| == |b|`. pub fn eqlAbs(a: Const, b: Const) bool { return orderAbs(a, b) == .eq; } /// Returns true if `a == b`. pub fn eql(a: Const, b: Const) bool { return order(a, b) == .eq; } /// Returns the number of leading zeros in twos-complement form. pub fn clz(a: Const, bits: Limb) Limb { // Limbs are stored in little-endian order but we need to iterate big-endian. if (!a.positive and !a.eqlZero()) return 0; var total_limb_lz: Limb = 0; var i: usize = a.limbs.len; const bits_per_limb = @bitSizeOf(Limb); while (i != 0) { i -= 1; const this_limb_lz = @clz(a.limbs[i]); total_limb_lz += this_limb_lz; if (this_limb_lz != bits_per_limb) break; } const total_limb_bits = a.limbs.len * bits_per_limb; return total_limb_lz + bits - total_limb_bits; } /// Returns the number of trailing zeros in twos-complement form. pub fn ctz(a: Const, bits: Limb) Limb { // Limbs are stored in little-endian order. Converting a negative number to twos-complement // flips all bits above the lowest set bit, which does not affect the trailing zero count. if (a.eqlZero()) return bits; var result: Limb = 0; for (a.limbs) |limb| { const limb_tz = @ctz(limb); result += limb_tz; if (limb_tz != @bitSizeOf(Limb)) break; } return @min(result, bits); } }