Skip to content

Commit e9b0a43

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

File tree

1 file changed

+80
-11
lines changed

1 file changed

+80
-11
lines changed

lib/std/fmt.zig

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2756,21 +2756,72 @@ 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
2772+
(!(x >= -(1 << (4 * result.len - 1)) and x < 1 << (4 * result.len)))
2773+
@compileError("out of bounds"),
2774+
.int => |int| if (int.bits > 4 * result.len) @compileError("out of bounds"),
2775+
else => @compileError("x is not an integer"),
2776+
};
2777+
2778+
const table = switch (options.case) {
2779+
.lower => "0123456789abcdef",
2780+
.upper => "0123456789ABCDEF",
2781+
};
2782+
2783+
_ = switch (options.endianness) {
2784+
.little => {
2785+
inline for (0..result.len / 2) |i| {
2786+
result[2 * i] = table[@as(u4, @truncate(x >> @min(4 * (2 * i + 1), x_bits - 1)))];
2787+
result[2 * i + 1] = table[@as(u4, @truncate(x >> @min(4 * 2 * i, x_bits - 1)))];
2788+
}
2789+
},
2790+
.big => .{inline for (0..result.len) |i| {
2791+
result[i] = table[@as(u4, @truncate(x >> @min(4 * (result.len - 1 - i), x_bits - 1)))];
2792+
}},
2793+
};
2794+
27702795
return result;
27712796
}
27722797

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

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

0 commit comments

Comments
 (0)