From ded4e125590f2e113f184acc1d79718ac21b83d9 Mon Sep 17 00:00:00 2001 From: unplanned Date: Wed, 3 Dec 2025 23:28:27 +0100 Subject: [PATCH 1/4] big.Const.dump: fixed error --- lib/std/math/big/int.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 5a00947ac3..003ea6ae4b 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2081,7 +2081,7 @@ pub const Const = struct { for (self.limbs[0..self.limbs.len]) |limb| { std.debug.print("{x} ", .{limb}); } - std.debug.print("len={} positive={}\n", .{ self.len, self.positive }); + std.debug.print("len={} positive={}\n", .{ self.limbs.len, self.positive }); } pub fn abs(self: Const) Const { From c6a14448646980cb1483bb17d38dcd9ae650c78e Mon Sep 17 00:00:00 2001 From: unplanned Date: Thu, 4 Dec 2025 00:24:45 +0100 Subject: [PATCH 2/4] std.math.big.int.int_test: replace mem.eql by expectEqualSlices --- lib/std/math/big/int_test.zig | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index eee6a0c7ce..7efd0f00c3 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -737,7 +737,7 @@ test "string to" { defer testing.allocator.free(as); const es = "120317241209124781241290847124"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "string to base base error" { @@ -755,7 +755,7 @@ test "string to base 2" { defer testing.allocator.free(as); const es = "-1011"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "string to base 16" { @@ -766,7 +766,7 @@ test "string to base 16" { defer testing.allocator.free(as); const es = "efffffff00000001eeeeeeefaaaaaaab"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "string to base 36" { @@ -777,7 +777,7 @@ test "string to base 36" { defer testing.allocator.free(as); const es = "fifvthrv1mzt79ez9"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "neg string to" { @@ -788,7 +788,7 @@ test "neg string to" { defer testing.allocator.free(as); const es = "-123907434"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "zero string to" { @@ -799,7 +799,7 @@ test "zero string to" { defer testing.allocator.free(as); const es = "0"; - try testing.expect(mem.eql(u8, as, es)); + try testing.expectEqualSlices(u8, es, as); } test "clone" { @@ -3404,26 +3404,26 @@ test "big int conversion read twos complement with padding" { var bit_count: usize = 12 * 8 + 1; a.toConst().writeTwosComplement(buffer1[0..13], .little); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0xaa, 0xaa, 0xaa })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0xaa, 0xaa, 0xaa }, buffer1); a.toConst().writeTwosComplement(buffer1[0..13], .big); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xaa, 0xaa, 0xaa })); + try testing.expectEqualSlices(u8, &[_]u8{ 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xaa, 0xaa, 0xaa }, buffer1); a.toConst().writeTwosComplement(buffer1[0..16], .little); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, 0x0, 0x0 })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xd, 0xc, 0xb, 0xa, 0x9, 0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0, 0x0, 0x0 }, buffer1); a.toConst().writeTwosComplement(buffer1[0..16], .big); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd })); + try testing.expectEqualSlices(u8, &[_]u8{ 0x0, 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd }, buffer1); @memset(buffer1, 0xaa); try a.set(-0x01_02030405_06070809_0a0b0c0d); bit_count = 12 * 8 + 2; a.toConst().writeTwosComplement(buffer1[0..13], .little); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaa, 0xaa, 0xaa })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xaa, 0xaa, 0xaa }, buffer1); a.toConst().writeTwosComplement(buffer1[0..13], .big); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3, 0xaa, 0xaa, 0xaa })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3, 0xaa, 0xaa, 0xaa }, buffer1); a.toConst().writeTwosComplement(buffer1[0..16], .little); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xf3, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff }, buffer1); a.toConst().writeTwosComplement(buffer1[0..16], .big); - try testing.expect(std.mem.eql(u8, buffer1, &[_]u8{ 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 })); + try testing.expectEqualSlices(u8, &[_]u8{ 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, 0xf3, 0xf3 }, buffer1); } test "big int write twos complement +/- zero" { @@ -3438,13 +3438,13 @@ test "big int write twos complement +/- zero" { // Test zero m.toConst().writeTwosComplement(buffer1[0..13], .little); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)), buffer1); m.toConst().writeTwosComplement(buffer1[0..13], .big); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)), buffer1); m.toConst().writeTwosComplement(buffer1[0..16], .little); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 16)), buffer1); m.toConst().writeTwosComplement(buffer1[0..16], .big); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 16)), buffer1); @memset(buffer1, 0xaa); m.positive = false; @@ -3452,13 +3452,13 @@ test "big int write twos complement +/- zero" { // Test negative zero m.toConst().writeTwosComplement(buffer1[0..13], .little); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)), buffer1); m.toConst().writeTwosComplement(buffer1[0..13], .big); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 13) ++ ([_]u8{0xaa} ** 3)), buffer1); m.toConst().writeTwosComplement(buffer1[0..16], .little); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 16)), buffer1); m.toConst().writeTwosComplement(buffer1[0..16], .big); - try testing.expect(std.mem.eql(u8, buffer1, &(([_]u8{0} ** 16)))); + try testing.expectEqualSlices(u8, &(([_]u8{0} ** 16)), buffer1); } test "big int conversion write twos complement with padding" { @@ -3816,7 +3816,7 @@ test "(BigInt) positive" { const b_fmt = try std.fmt.allocPrint(testing.allocator, "{d}", .{b}); defer testing.allocator.free(b_fmt); - try testing.expect(!mem.eql(u8, b_fmt, "(BigInt)")); + try testing.expect(!mem.eql(u8, "(BigInt)", b_fmt)); } test "(BigInt) negative" { @@ -3840,7 +3840,7 @@ test "(BigInt) negative" { const b_fmt = try std.fmt.allocPrint(testing.allocator, "{d}", .{b}); defer testing.allocator.free(b_fmt); - try testing.expect(mem.eql(u8, a_fmt, "(BigInt)")); + try testing.expectEqualSlices(u8, "(BigInt)", a_fmt); try testing.expect(!mem.eql(u8, b_fmt, "(BigInt)")); } From 73e82332d02e42a8b95f5c30652d4b7d0db7bea6 Mon Sep 17 00:00:00 2001 From: unplanned Date: Thu, 4 Dec 2025 01:00:55 +0100 Subject: [PATCH 3/4] big.Mutable.setString optimization and simplification --- lib/std/math/big/int.zig | 68 ++++++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 003ea6ae4b..34429118c2 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -17,6 +17,27 @@ const Endian = std.builtin.Endian; const Signedness = std.builtin.Signedness; const native_endian = builtin.cpu.arch.endian(); +// Comptime-computed constants for supported bases (2 - 36) +// all values are set to 0 for bases 0 - 1, to make it possible to +// access a constant for a given base b using `constants.value[b]` +const Constants = struct { + // big_bases[b] is the biggest power of b that fit in a single Limb + // i.e. big_bases[b] = b^k < 2^@bitSizeOf(Limb) and b^(k+1) >= 2^@bitSizeOf(Limb) + big_bases: [37]Limb, + // digits_per_limb[b] is the value of k used in the previous field + digits_per_limb: [37]u8, +}; +const constants: Constants = blk: { + @setEvalBranchQuota(2000); + var digits_per_limb = [_]u8{0} ** 37; + var bases = [_]Limb{0} ** 37; + for (2..37) |base| { + digits_per_limb[base] = @intCast(math.log(Limb, base, math.maxInt(Limb))); + bases[base] = std.math.pow(Limb, base, digits_per_limb[base]); + } + break :blk Constants{ .big_bases = bases, .digits_per_limb = digits_per_limb }; +}; + /// Returns the number of limbs needed to store `scalar`, which must be a /// primitive integer or float value. /// Note: A comptime-known upper bound of this value that may be used @@ -329,23 +350,15 @@ pub const Mutable = struct { /// not allowed (e.g. 0x43 should simply be 43). Underscores in the input string are /// ignored and can be used as digit separators. /// - /// Asserts there is enough memory for the value in `self.limbs`. An upper bound on number of limbs can + /// There must be enough memory for the value in `self.limbs`. An upper bound on number of limbs can /// be determined with `calcSetStringLimbCount`. /// Asserts the base is in the range [2, 36]. /// /// Returns an error if the value has invalid digits for the requested base. - /// - /// `limbs_buffer` is used for temporary storage. The size required can be found with - /// `calcSetStringLimbsBufferLen`. - /// - /// If `allocator` is provided, it will be used for temporary storage to improve - /// multiplication performance. `error.OutOfMemory` is handled with a fallback algorithm. pub fn setString( self: *Mutable, base: u8, value: []const u8, - limbs_buffer: []Limb, - allocator: ?Allocator, ) error{InvalidCharacter}!void { assert(base >= 2); assert(base <= 36); @@ -357,18 +370,41 @@ pub const Mutable = struct { i += 1; } - const ap_base: Const = .{ .limbs = &[_]Limb{base}, .positive = true }; - self.set(0); + @memset(self.limbs, 0); + self.len = 1; + var limb: Limb = 0; + var j: usize = 0; for (value[i..]) |ch| { if (ch == '_') { continue; } const d = try std.fmt.charToDigit(ch, base); - const ap_d: Const = .{ .limbs = &[_]Limb{d}, .positive = true }; + limb *= base; + limb += d; + j += 1; - self.mul(self.toConst(), ap_base, limbs_buffer, allocator); - self.add(self.toConst(), ap_d); + if (j == constants.digits_per_limb[base]) { + const len = @min(self.len + 1, self.limbs.len); + // r = a * b = a + a * (b - 1) + // we assert when self.limbs is not large enough to store the number + assert(!llmulLimb(.add, self.limbs[0..len], self.limbs[0..len], constants.big_bases[base] - 1)); + assert(lladdcarry(self.limbs[0..len], self.limbs[0..len], &[1]Limb{limb}) == 0); + + if (self.limbs.len > self.len and self.limbs[self.len] != 0) + self.len += 1; + j = 0; + limb = 0; + } + } + if (j > 0) { + const len = @min(self.len + 1, self.limbs.len); + // we assert when self.limbs is not large enough to store the number + assert(!llmulLimb(.add, self.limbs[0..len], self.limbs[0..len], math.pow(Limb, base, j) - 1)); + assert(lladdcarry(self.limbs[0..len], self.limbs[0..len], &[1]Limb{limb}) == 0); + + if (self.limbs.len > self.len and self.limbs[self.len] != 0) + self.len += 1; } self.positive = positive; } @@ -2884,10 +2920,8 @@ pub const Managed = struct { pub fn setString(self: *Managed, base: u8, value: []const u8) !void { if (base < 2 or base > 36) return error.InvalidBase; try self.ensureCapacity(calcSetStringLimbCount(base, value.len)); - const limbs_buffer = try self.allocator.alloc(Limb, calcSetStringLimbsBufferLen(base, value.len)); - defer self.allocator.free(limbs_buffer); var m = self.toMutable(); - try m.setString(base, value, limbs_buffer, self.allocator); + try m.setString(base, value); self.setMetadata(m.positive, m.len); } From 688af047255790d9aec4a1152eaa525f7dd5b46d Mon Sep 17 00:00:00 2001 From: unplanned Date: Thu, 4 Dec 2025 01:05:17 +0100 Subject: [PATCH 4/4] math.big: stronger asserts to reduce risks of aliasing --- lib/std/math/big/int.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 34429118c2..a6b38aac63 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -3630,6 +3630,7 @@ fn llmulaccKaratsuba( /// r = r (op) a. /// The result is computed modulo `r.len`. fn llaccum(comptime op: AccOp, r: []Limb, a: []const Limb) void { + assert(!slicesOverlap(r, a) or @intFromPtr(r.ptr) <= @intFromPtr(a.ptr)); if (op == .sub) { _ = llsubcarry(r, r, a); return; @@ -3699,6 +3700,8 @@ fn llmulaccLong(comptime op: AccOp, r: []Limb, a: []const Limb, b: []const Limb) /// The result is computed modulo `r.len`. /// Returns whether the operation overflowed. fn llmulLimb(comptime op: AccOp, acc: []Limb, y: []const Limb, xi: Limb) bool { + assert(!slicesOverlap(acc, y) or @intFromPtr(acc.ptr) <= @intFromPtr(y.ptr)); + if (xi == 0) { return false; } @@ -3761,6 +3764,8 @@ fn llsubcarry(r: []Limb, a: []const Limb, b: []const Limb) Limb { assert(a.len != 0 and b.len != 0); assert(a.len >= b.len); assert(r.len >= a.len); + assert(!slicesOverlap(r, a) or @intFromPtr(r.ptr) <= @intFromPtr(a.ptr)); + assert(!slicesOverlap(r, b) or @intFromPtr(r.ptr) <= @intFromPtr(b.ptr)); var i: usize = 0; var borrow: Limb = 0; @@ -3792,6 +3797,8 @@ fn lladdcarry(r: []Limb, a: []const Limb, b: []const Limb) Limb { assert(a.len != 0 and b.len != 0); assert(a.len >= b.len); assert(r.len >= a.len); + assert(!slicesOverlap(r, a) or @intFromPtr(r.ptr) <= @intFromPtr(a.ptr)); + assert(!slicesOverlap(r, b) or @intFromPtr(r.ptr) <= @intFromPtr(b.ptr)); var i: usize = 0; var carry: Limb = 0;