Skip to content

Commit 6acc69d

Browse files
committed
std.fmt: add hexOptions, make hex work with all runtime integer sizes
Fixes #23799
1 parent 10bf696 commit 6acc69d

File tree

1 file changed

+79
-11
lines changed

1 file changed

+79
-11
lines changed

lib/std/fmt.zig

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,21 +2756,71 @@ test "recursive format function" {
27562756

27572757
pub const hex_charset = "0123456789abcdef";
27582758

2759-
/// Converts an unsigned integer of any multiple of u8 to an array of lowercase
2760-
/// hex bytes, little endian.
2761-
pub fn hex(x: anytype) [@sizeOf(@TypeOf(x)) * 2]u8 {
2762-
comptime assert(@typeInfo(@TypeOf(x)).int.signedness == .unsigned);
2763-
var result: [@sizeOf(@TypeOf(x)) * 2]u8 = undefined;
2764-
var i: usize = 0;
2765-
while (i < result.len / 2) : (i += 1) {
2766-
const byte: u8 = @truncate(x >> @intCast(8 * i));
2767-
result[i * 2 + 0] = hex_charset[byte >> 4];
2768-
result[i * 2 + 1] = hex_charset[byte & 15];
2769-
}
2759+
/// Deprecated, use `std.fmt.hexOptions(x, .{ .endianness = .little })` instead.
2760+
pub fn hex(x: anytype) HexOptionsResult(@TypeOf(x), .{ .endianness = .little }) {
2761+
return hexOptions(x, .{ .endianness = .little });
2762+
}
2763+
2764+
/// Converts an integer to an array of hex characters.
2765+
pub fn hexOptions(x: anytype, comptime options: HexOptions) HexOptionsResult(@TypeOf(x), options) {
2766+
var result: HexOptionsResult(@TypeOf(x), options) = undefined;
2767+
2768+
const x_bits = if (@TypeOf(x) == comptime_int) 4 * result.len else @typeInfo(@TypeOf(x)).int.bits;
2769+
2770+
comptime switch (@typeInfo(@TypeOf(x))) {
2771+
.comptime_int => if (!(x >= -(1 << (4 * result.len - 1)) and x < 1 << (4 * result.len)))
2772+
@compileError("out of bounds"),
2773+
.int => |int| if (int.bits > 4 * result.len) @compileError("out of bounds"),
2774+
else => @compileError("x is not an integer"),
2775+
};
2776+
2777+
const table = switch (options.case) {
2778+
.lower => "0123456789abcdef",
2779+
.upper => "0123456789ABCDEF",
2780+
};
2781+
2782+
_ = switch (options.endianness) {
2783+
.little => {
2784+
inline for (0..result.len / 2) |i| {
2785+
result[2 * i] = table[@as(u4, @truncate(x >> @min(4 * (2 * i + 1), x_bits - 1)))];
2786+
result[2 * i + 1] = table[@as(u4, @truncate(x >> @min(4 * 2 * i, x_bits - 1)))];
2787+
}
2788+
},
2789+
.big => .{inline for (0..result.len) |i| {
2790+
result[i] = table[@as(u4, @truncate(x >> @min(4 * (result.len - 1 - i), x_bits - 1)))];
2791+
}},
2792+
};
2793+
27702794
return result;
27712795
}
27722796

2797+
pub fn HexOptionsResult(x: type, comptime options: HexOptions) type {
2798+
const info = @typeInfo(x);
2799+
2800+
const hex_count = 2 * (options.octet_count orelse comptime switch (info) {
2801+
.int => |int| (int.bits + 7) / 8,
2802+
.comptime_int => @compileError("std.fmt.hex on comptime_int must have a known size"),
2803+
else => @compileError("type must be an integer"),
2804+
});
2805+
2806+
return [hex_count]u8;
2807+
}
2808+
2809+
pub const HexOptions = struct {
2810+
/// Whether octets are ordered little-endian or big-endian
2811+
endianness: std.builtin.Endian = .big,
2812+
case: enum { lower, upper } = .lower,
2813+
/// Sign pads the result to the number of octets.
2814+
/// Required for `comptime_int`.
2815+
octet_count: ?usize = null,
2816+
};
2817+
27732818
test hex {
2819+
{
2820+
const x = hex(@as(u48, 0x1234_5678_abcd));
2821+
try std.testing.expect(x.len == 12);
2822+
try std.testing.expectEqualStrings("cdab78563412", &x);
2823+
}
27742824
{
27752825
const x = hex(@as(u32, 0xdeadbeef));
27762826
try std.testing.expect(x.len == 8);
@@ -2783,6 +2833,24 @@ test hex {
27832833
}
27842834
}
27852835

2836+
test hexOptions {
2837+
{
2838+
const x = hexOptions(@as(u48, 0x1234_5678_abcd), .{ .octet_count = 8 });
2839+
try std.testing.expect(x.len == 16);
2840+
try std.testing.expectEqualStrings("000012345678abcd", &x);
2841+
}
2842+
{
2843+
const x = hexOptions(@as(u32, 0xdeadbeef), .{ .endianness = .little, .case = .upper });
2844+
try std.testing.expect(x.len == 8);
2845+
try std.testing.expectEqualStrings("EFBEADDE", &x);
2846+
}
2847+
{
2848+
const x = hexOptions(-0x3047abed, .{ .octet_count = 8, .endianness = .little });
2849+
try std.testing.expect(x.len == 16);
2850+
try std.testing.expectEqualStrings("1354b8cfffffffff", &x);
2851+
}
2852+
}
2853+
27862854
test "parser until" {
27872855
{ // return substring till ':'
27882856
var parser: Parser = .{

0 commit comments

Comments
 (0)