authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-09-17 00:41:26+02:00
committergravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-09-17 00:53:08+02:00
log27adb82fda516e95c3c03df80a95b895969fdd56
treed906389a448b260b4c64bf101fab822944ed4105
parentbb9a4ad6e903ad2f9c137bca56bdf2dd6fc65d03

std: Respect user-specified alignment when formatting ints

This implementation tries to do the right thing (TM) by treating the sign as part of the number itself, therefore the alignment parameter applies to both the sign and the digits. In other words the format string `{:>4}` with -1 as input will not output `- 1` but ` -1`. And let's default to right alignment for everything as that's what users want, especially when printing numbers. Many implementations use different defaults for numeric vs non-numeric types, let's strive for a consistent behaviour here.

1 files changed, 51 insertions(+), 82 deletions(-)

lib/std/fmt.zig+51-82
......@@ -22,7 +22,7 @@ pub const Alignment = enum {
2222pub const FormatOptions = struct {
2323 precision: ?usize = null,
2424 width: ?usize = null,
25 alignment: Alignment = .Left,
25 alignment: Alignment = .Right,
2626 fill: u8 = ' ',
2727};
2828
......@@ -631,26 +631,22 @@ pub fn formatBuf(
631631 writer: anytype,
632632) !void {
633633 const width = options.width orelse buf.len;
634 var padding = if (width > buf.len) (width - buf.len) else 0;
635 const pad_byte = [1]u8{options.fill};
634 const padding = if (width > buf.len) (width - buf.len) else 0;
635
636636 switch (options.alignment) {
637637 .Left => {
638638 try writer.writeAll(buf);
639 while (padding > 0) : (padding -= 1) {
640 try writer.writeAll(&pad_byte);
641 }
639 try writer.writeByteNTimes(options.fill, padding);
642640 },
643641 .Center => {
644 const padl = padding / 2;
645 var i: usize = 0;
646 while (i < padl) : (i += 1) try writer.writeAll(&pad_byte);
642 const left_padding = padding / 2;
643 const right_padding = (padding + 1) / 2;
644 try writer.writeByteNTimes(options.fill, left_padding);
647645 try writer.writeAll(buf);
648 while (i < padding) : (i += 1) try writer.writeAll(&pad_byte);
646 try writer.writeByteNTimes(options.fill, right_padding);
649647 },
650648 .Right => {
651 while (padding > 0) : (padding -= 1) {
652 try writer.writeAll(&pad_byte);
653 }
649 try writer.writeByteNTimes(options.fill, padding);
654650 try writer.writeAll(buf);
655651 },
656652 }
......@@ -941,61 +937,27 @@ pub fn formatInt(
941937 options: FormatOptions,
942938 writer: anytype,
943939) !void {
940 assert(base >= 2);
941
944942 const int_value = if (@TypeOf(value) == comptime_int) blk: {
945943 const Int = math.IntFittingRange(value, value);
946944 break :blk @as(Int, value);
947945 } else
948946 value;
949947
950 if (@typeInfo(@TypeOf(int_value)).Int.is_signed) {
951 return formatIntSigned(int_value, base, uppercase, options, writer);
952 } else {
953 return formatIntUnsigned(int_value, base, uppercase, options, writer);
954 }
955}
948 const value_info = @typeInfo(@TypeOf(int_value)).Int;
956949
957fn formatIntSigned(
958 value: anytype,
959 base: u8,
960 uppercase: bool,
961 options: FormatOptions,
962 writer: anytype,
963) !void {
964 const new_options = FormatOptions{
965 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
966 .precision = options.precision,
967 .fill = options.fill,
968 };
969 const bit_count = @typeInfo(@TypeOf(value)).Int.bits;
970 const Uint = std.meta.Int(false, bit_count);
971 if (value < 0) {
972 try writer.writeAll("-");
973 const new_value = math.absCast(value);
974 return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
975 } else if (options.width == null or options.width.? == 0) {
976 return formatIntUnsigned(@intCast(Uint, value), base, uppercase, options, writer);
977 } else {
978 try writer.writeAll("+");
979 const new_value = @intCast(Uint, value);
980 return formatIntUnsigned(new_value, base, uppercase, new_options, writer);
981 }
982}
950 // The type must have the same size as `base` or be wider in order for the
951 // division to work
952 const min_int_bits = comptime math.max(value_info.bits, 8);
953 const MinInt = std.meta.Int(false, min_int_bits);
983954
984fn formatIntUnsigned(
985 value: anytype,
986 base: u8,
987 uppercase: bool,
988 options: FormatOptions,
989 writer: anytype,
990) !void {
991 assert(base >= 2);
992 const value_info = @typeInfo(@TypeOf(value)).Int;
993 var buf: [math.max(value_info.bits, 1)]u8 = undefined;
994 const min_int_bits = comptime math.max(value_info.bits, @typeInfo(@TypeOf(base)).Int.bits);
995 const MinInt = std.meta.Int(value_info.is_signed, min_int_bits);
996 var a: MinInt = value;
997 var index: usize = buf.len;
955 const abs_value = math.absCast(int_value);
956 // The worst case in terms of space needed is base 2, plus 1 for the sign
957 var buf: [1 + math.max(value_info.bits, 1)]u8 = undefined;
998958
959 var a: MinInt = abs_value;
960 var index: usize = buf.len;
999961 while (true) {
1000962 const digit = a % base;
1001963 index -= 1;
......@@ -1004,25 +966,21 @@ fn formatIntUnsigned(
1004966 if (a == 0) break;
1005967 }
1006968
1007 const digits_buf = buf[index..];
1008 const width = options.width orelse 0;
1009 const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0;
1010
1011 if (padding > index) {
1012 const zero_byte: u8 = options.fill;
1013 var leftover_padding = padding - index;
1014 while (true) {
1015 try writer.writeAll(@as(*const [1]u8, &zero_byte)[0..]);
1016 leftover_padding -= 1;
1017 if (leftover_padding == 0) break;
969 if (value_info.is_signed) {
970 if (value < 0) {
971 // Negative integer
972 index -= 1;
973 buf[index] = '-';
974 } else if (options.width == null or options.width.? == 0) {
975 // Positive integer, omit the plus sign
976 } else {
977 // Positive integer
978 index -= 1;
979 buf[index] = '+';
1018980 }
1019 mem.set(u8, buf[0..index], options.fill);
1020 return writer.writeAll(&buf);
1021 } else {
1022 const padded_buf = buf[index - padding ..];
1023 mem.set(u8, padded_buf[0..padding], options.fill);
1024 return writer.writeAll(padded_buf);
1025981 }
982
983 return formatBuf(buf[index..], options, writer);
1026984}
1027985
1028986pub fn formatIntBuf(out_buf: []u8, value: anytype, base: u8, uppercase: bool, options: FormatOptions) usize {
......@@ -1287,7 +1245,17 @@ test "int.specifier" {
12871245
12881246test "int.padded" {
12891247 try testFmt("u8: ' 1'", "u8: '{:4}'", .{@as(u8, 1)});
1290 try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", .{@as(u8, 1)});
1248 try testFmt("u8: '1000'", "u8: '{:0<4}'", .{@as(u8, 1)});
1249 try testFmt("u8: '0001'", "u8: '{:0>4}'", .{@as(u8, 1)});
1250 try testFmt("u8: '0100'", "u8: '{:0^4}'", .{@as(u8, 1)});
1251 try testFmt("i8: '-1 '", "i8: '{:<4}'", .{@as(i8, -1)});
1252 try testFmt("i8: ' -1'", "i8: '{:>4}'", .{@as(i8, -1)});
1253 try testFmt("i8: ' -1 '", "i8: '{:^4}'", .{@as(i8, -1)});
1254 try testFmt("i16: '-1234'", "i16: '{:4}'", .{@as(i16, -1234)});
1255 try testFmt("i16: '+1234'", "i16: '{:4}'", .{@as(i16, 1234)});
1256 try testFmt("i16: '-12345'", "i16: '{:4}'", .{@as(i16, -12345)});
1257 try testFmt("i16: '+12345'", "i16: '{:4}'", .{@as(i16, 12345)});
1258 try testFmt("u16: '12345'", "u16: '{:4}'", .{@as(u16, 12345)});
12911259}
12921260
12931261test "buffer" {
......@@ -1333,7 +1301,7 @@ test "slice" {
13331301 try testFmt("slice: []const u8@deadbeef\n", "slice: {}\n", .{value});
13341302 }
13351303
1336 try testFmt("buf: Test \n", "buf: {s:5}\n", .{"Test"});
1304 try testFmt("buf: Test\n", "buf: {s:5}\n", .{"Test"});
13371305 try testFmt("buf: Test\n Other text", "buf: {s}\n Other text", .{"Test"});
13381306}
13391307
......@@ -1366,7 +1334,7 @@ test "cstr" {
13661334 .{@ptrCast([*c]const u8, "Test C")},
13671335 );
13681336 try testFmt(
1369 "cstr: Test C \n",
1337 "cstr: Test C\n",
13701338 "cstr: {s:10}\n",
13711339 .{@ptrCast([*c]const u8, "Test C")},
13721340 );
......@@ -1809,7 +1777,7 @@ test "vector" {
18091777
18101778 try testFmt("{ true, false, true, false }", "{}", .{vbool});
18111779 try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64});
1812 try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64});
1780 try testFmt("{ -2, -1, +0, +1 }", "{d:5}", .{vi64});
18131781 try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64});
18141782 try testFmt("{ 3e8, 7d0, bb8, fa0 }", "{x}", .{vu64});
18151783 try testFmt("{ 1kB, 2kB, 3kB, 4kB }", "{B}", .{vu64});
......@@ -1822,15 +1790,16 @@ test "enum-literal" {
18221790
18231791test "padding" {
18241792 try testFmt("Simple", "{}", .{"Simple"});
1825 try testFmt("true ", "{:10}", .{true});
1793 try testFmt(" true", "{:10}", .{true});
18261794 try testFmt(" true", "{:>10}", .{true});
18271795 try testFmt("======true", "{:=>10}", .{true});
18281796 try testFmt("true======", "{:=<10}", .{true});
18291797 try testFmt(" true ", "{:^10}", .{true});
18301798 try testFmt("===true===", "{:=^10}", .{true});
1831 try testFmt("Minimum width", "{:18} width", .{"Minimum"});
1799 try testFmt(" Minimum width", "{:18} width", .{"Minimum"});
18321800 try testFmt("==================Filled", "{:=>24}", .{"Filled"});
18331801 try testFmt(" Centered ", "{:^24}", .{"Centered"});
1802 try testFmt("-", "{:-^1}", .{""});
18341803}
18351804
18361805test "decimal float padding" {