From 51a9a6aab696b9fbd69dadb5a569cfe184fabecc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 9 Jul 2025 14:54:54 -0700 Subject: [PATCH] std: replace formatInteger with formatNumber --- lib/std/fmt.zig | 39 ++++++++++++++++++++ lib/std/io/Writer.zig | 77 +++++++++++++++++++++++++--------------- lib/std/math/big/int.zig | 17 ++++----- 3 files changed, 96 insertions(+), 37 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index aa6b5c1670..f40b763f9f 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -37,6 +37,45 @@ pub const Options = struct { width: ?usize = null, alignment: Alignment = default_alignment, fill: u8 = default_fill_char, + + pub fn toNumber(o: Options, mode: Number.Mode, case: Case) Number { + return .{ + .mode = mode, + .case = case, + .precision = o.precision, + .width = o.width, + .alignment = o.alignment, + .fill = o.fill, + }; + } +}; + +pub const Number = struct { + mode: Mode = .decimal, + /// Affects hex digits as well as floating point "inf"/"INF". + case: Case = .lower, + precision: ?usize = null, + width: ?usize = null, + alignment: Alignment = default_alignment, + fill: u8 = default_fill_char, + + pub const Mode = enum { + decimal, + binary, + octal, + hex, + scientific, + + pub fn base(mode: Mode) ?u8 { + return switch (mode) { + .decimal => 10, + .binary => 2, + .octal => 8, + .hex => 16, + .scientific => null, + }; + } + }; }; /// Renders fmt string with args, calling `writer` with slices of bytes. diff --git a/lib/std/io/Writer.zig b/lib/std/io/Writer.zig index fa2380edd3..55415621d0 100644 --- a/lib/std/io/Writer.zig +++ b/lib/std/io/Writer.zig @@ -744,11 +744,8 @@ pub fn printAddress(w: *Writer, value: anytype) Error!void { switch (@typeInfo(T)) { .pointer => |info| { try w.writeAll(@typeName(info.child) ++ "@"); - if (info.size == .slice) - try w.printInt(@intFromPtr(value.ptr), 16, .lower, .{}) - else - try w.printInt(@intFromPtr(value), 16, .lower, .{}); - return; + const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value); + return w.printInt(int, 16, .lower, .{}); }, .optional => |info| { if (@typeInfo(info.child) == .pointer) { @@ -777,9 +774,9 @@ pub fn printValue( '*' => return w.printAddress(value), 'f' => return value.format(w), 'd' => switch (@typeInfo(T)) { - .float, .comptime_float => return printFloat(w, value, .decimal, options), + .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)), .int, .comptime_int => return printInt(w, value, 10, .lower, options), - .@"struct" => return value.formatInteger(w, 10, .lower), + .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)), .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options), .vector => return printVector(w, fmt, options, value, max_depth), else => invalidFmtError(fmt, value), @@ -789,22 +786,22 @@ pub fn printValue( 'b' => switch (@typeInfo(T)) { .int, .comptime_int => return printInt(w, value, 2, .lower, options), .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options), - .@"struct" => return value.formatInteger(w, 2, .lower), + .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)), .vector => return printVector(w, fmt, options, value, max_depth), else => invalidFmtError(fmt, value), }, 'o' => switch (@typeInfo(T)) { .int, .comptime_int => return printInt(w, value, 8, .lower, options), .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options), - .@"struct" => return value.formatInteger(w, 8, .lower), + .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)), .vector => return printVector(w, fmt, options, value, max_depth), else => invalidFmtError(fmt, value), }, 'x' => switch (@typeInfo(T)) { - .float, .comptime_float => return printFloatHexOptions(w, value, .lower, options), + .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), .int, .comptime_int => return printInt(w, value, 16, .lower, options), .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options), - .@"struct" => return value.formatInteger(w, 16, .lower), + .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)), .pointer => |info| switch (info.size) { .one, .slice => { const slice: []const u8 = value; @@ -823,10 +820,10 @@ pub fn printValue( else => invalidFmtError(fmt, value), }, 'X' => switch (@typeInfo(T)) { - .float, .comptime_float => return printFloatHexOptions(w, value, .lower, options), + .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)), .int, .comptime_int => return printInt(w, value, 16, .upper, options), .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options), - .@"struct" => return value.formatInteger(w, 16, .upper), + .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)), .pointer => |info| switch (info.size) { .one, .slice => { const slice: []const u8 = value; @@ -872,8 +869,13 @@ pub fn printValue( else => invalidFmtError(fmt, value), }, 'e' => switch (@typeInfo(T)) { - .float, .comptime_float => return printFloat(w, value, .scientific, options), - .@"struct" => return value.formatFloat(w, .scientific), + .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)), + .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)), + else => invalidFmtError(fmt, value), + }, + 'E' => switch (@typeInfo(T)) { + .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)), + .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)), else => invalidFmtError(fmt, value), }, 't' => switch (@typeInfo(T)) { @@ -923,7 +925,7 @@ pub fn printValue( switch (@typeInfo(T)) { .float, .comptime_float => { if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); - return printFloat(w, value, .decimal, options); + return printFloat(w, value, options.toNumber(.decimal, .lower)); }, .int, .comptime_int => { if (!is_any and fmt.len != 0) invalidFmtError(fmt, value); @@ -1262,12 +1264,13 @@ pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void { return w.writeAll(buf[0..len]); } -pub fn printFloat( - w: *Writer, - value: anytype, - mode: std.fmt.float.Mode, - options: std.fmt.Options, -) Error!void { +/// Uses a larger stack buffer; asserts mode is decimal or scientific. +pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { + const mode: std.fmt.float.Mode = switch (options.mode) { + .decimal => .decimal, + .scientific => .scientific, + .binary, .octal, .hex => unreachable, + }; var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined; const s = std.fmt.float.render(&buf, value, .{ .mode = mode, @@ -1275,20 +1278,36 @@ pub fn printFloat( }) catch |err| switch (err) { error.BufferTooSmall => "(float)", }; - return w.alignBufferOptions(s, options); + return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill); } -pub fn printFloatHexOptions(w: *Writer, value: anytype, case: std.fmt.Case, options: std.fmt.Options) Error!void { +/// Uses a smaller stack buffer; asserts mode is not decimal or scientific. +pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void { var buf: [50]u8 = undefined; // for aligning var sub_writer: Writer = .fixed(&buf); - printFloatHex(&sub_writer, value, case, options.precision) catch unreachable; // buf is large enough - return w.alignBufferOptions(sub_writer.buffered(), options); + switch (options.mode) { + .decimal => unreachable, + .scientific => unreachable, + .binary => @panic("TODO"), + .octal => @panic("TODO"), + .hex => {}, + } + printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough + + const printed = sub_writer.buffered(); + return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill); } pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void { if (std.math.signbit(value)) try w.writeByte('-'); - if (std.math.isNan(value)) return w.writeAll("nan"); - if (std.math.isInf(value)) return w.writeAll("inf"); + if (std.math.isNan(value)) return w.writeAll(switch (case) { + .lower => "nan", + .upper => "NAN", + }); + if (std.math.isInf(value)) return w.writeAll(switch (case) { + .lower => "inf", + .upper => "INF", + }); const T = @TypeOf(value); const TU = std.meta.Int(.unsigned, @bitSizeOf(T)); @@ -1822,7 +1841,7 @@ test printInt { test "printFloat with comptime_float" { var buf: [20]u8 = undefined; var w: Writer = .fixed(&buf); - try w.printFloat(@as(comptime_float, 1.0), .scientific, .{}); + try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower)); try std.testing.expectEqualStrings(w.buffered(), "1e0"); try std.testing.expectFmt("1", "{}", .{1.0}); } diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index c657aeeed3..33938dbd42 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -2030,11 +2030,11 @@ pub const Mutable = struct { } pub fn format(self: Mutable, w: *std.io.Writer) std.io.Writer.Error!void { - return formatInteger(self, w, 10, .lower); + return formatNumber(self, w, .{}); } - pub fn formatInteger(self: Const, w: *std.io.Writer, base: u8, case: std.fmt.Case) std.io.Writer.Error!void { - return self.toConst().formatInteger(w, base, case); + pub fn formatNumber(self: Const, w: *std.io.Writer, n: std.fmt.Number) std.io.Writer.Error!void { + return self.toConst().formatNumber(w, n); } }; @@ -2329,7 +2329,7 @@ pub const Const = struct { /// 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 formatInteger(self: Const, w: *std.io.Writer, base: u8, case: std.fmt.Case) std.io.Writer.Error!void { + pub fn formatNumber(self: Const, w: *std.io.Writer, number: std.fmt.Number) std.io.Writer.Error!void { const available_len = 64; if (self.limbs.len > available_len) return w.writeAll("(BigInt)"); @@ -2341,7 +2341,8 @@ pub const Const = struct { .positive = false, }; var buf: [biggest.sizeInBaseUpperBound(2)]u8 = undefined; - const len = self.toString(&buf, base, case, &limbs); + const base: u8 = number.mode.base() orelse @panic("TODO print big int in scientific form"); + const len = self.toString(&buf, base, number.case, &limbs); return w.writeAll(buf[0..len]); } @@ -2913,15 +2914,15 @@ pub const Managed = struct { /// To allow `std.fmt.format` to work with `Managed`. pub fn format(self: Managed, w: *std.io.Writer) std.io.Writer.Error!void { - return formatInteger(self, w, 10, .lower); + return formatNumber(self, w, .{}); } /// 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 formatInteger(self: Managed, w: *std.io.Writer, base: u8, case: std.fmt.Case) std.io.Writer.Error!void { - return self.toConst().formatInteger(w, base, case); + pub fn formatNumber(self: Managed, w: *std.io.Writer, n: std.fmt.Number) std.io.Writer.Error!void { + return self.toConst().formatNumber(w, n); } /// Returns math.Order.lt, math.Order.eq, math.Order.gt if |a| < |b|, |a| ==