| ... | @@ -696,6 +696,11 @@ fn formatFloatValue( | ... | @@ -696,6 +696,11 @@ fn formatFloatValue( |
| 696 | error.NoSpaceLeft => unreachable, | 696 | error.NoSpaceLeft => unreachable, |
| 697 | else => |e| return e, | 697 | else => |e| return e, |
| 698 | }; | 698 | }; |
| | 699 | } else if (comptime std.mem.eql(u8, fmt, "x")) { |
| | 700 | formatFloatHexadecimal(value, options, buf_stream.writer()) catch |err| switch (err) { |
| | 701 | error.NoSpaceLeft => unreachable, |
| | 702 | else => |e| return e, |
| | 703 | }; |
| 699 | } else { | 704 | } else { |
| 700 | @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); | 705 | @compileError("Unsupported format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'"); |
| 701 | } | 706 | } |
| ... | @@ -1023,6 +1028,112 @@ pub fn formatFloatScientific( | ... | @@ -1023,6 +1028,112 @@ pub fn formatFloatScientific( |
| 1023 | } | 1028 | } |
| 1024 | } | 1029 | } |
| 1025 | | 1030 | |
| | 1031 | pub fn formatFloatHexadecimal( |
| | 1032 | value: anytype, |
| | 1033 | options: FormatOptions, |
| | 1034 | writer: anytype, |
| | 1035 | ) !void { |
| | 1036 | if (math.signbit(value)) { |
| | 1037 | try writer.writeByte('-'); |
| | 1038 | } |
| | 1039 | if (math.isNan(value)) { |
| | 1040 | return writer.writeAll("nan"); |
| | 1041 | } |
| | 1042 | if (math.isInf(value)) { |
| | 1043 | return writer.writeAll("inf"); |
| | 1044 | } |
| | 1045 | |
| | 1046 | const T = @TypeOf(value); |
| | 1047 | const TU = std.meta.Int(.unsigned, std.meta.bitCount(T)); |
| | 1048 | |
| | 1049 | const mantissa_bits = math.floatMantissaBits(T); |
| | 1050 | const exponent_bits = math.floatExponentBits(T); |
| | 1051 | const mantissa_mask = (1 << mantissa_bits) - 1; |
| | 1052 | const exponent_mask = (1 << exponent_bits) - 1; |
| | 1053 | const exponent_bias = (1 << (exponent_bits - 1)) - 1; |
| | 1054 | |
| | 1055 | const as_bits = @bitCast(TU, value); |
| | 1056 | var mantissa = as_bits & mantissa_mask; |
| | 1057 | var exponent: i32 = @truncate(u16, (as_bits >> mantissa_bits) & exponent_mask); |
| | 1058 | |
| | 1059 | const is_denormal = exponent == 0 and mantissa != 0; |
| | 1060 | const is_zero = exponent == 0 and mantissa == 0; |
| | 1061 | |
| | 1062 | if (is_zero) { |
| | 1063 | // Handle this case here to simplify the logic below. |
| | 1064 | try writer.writeAll("0x0"); |
| | 1065 | if (options.precision) |precision| { |
| | 1066 | if (precision > 0) { |
| | 1067 | try writer.writeAll("."); |
| | 1068 | try writer.writeByteNTimes('0', precision); |
| | 1069 | } |
| | 1070 | } else { |
| | 1071 | try writer.writeAll(".0"); |
| | 1072 | } |
| | 1073 | try writer.writeAll("p0"); |
| | 1074 | return; |
| | 1075 | } |
| | 1076 | |
| | 1077 | if (is_denormal) { |
| | 1078 | // Adjust the exponent for printing. |
| | 1079 | exponent += 1; |
| | 1080 | } else { |
| | 1081 | // Add the implicit 1. |
| | 1082 | mantissa |= 1 << mantissa_bits; |
| | 1083 | } |
| | 1084 | |
| | 1085 | // Fill in zeroes to round the mantissa width to a multiple of 4. |
| | 1086 | if (T == f16) mantissa <<= 2 else if (T == f32) mantissa <<= 1; |
| | 1087 | |
| | 1088 | const mantissa_digits = (mantissa_bits + 3) / 4; |
| | 1089 | |
| | 1090 | if (options.precision) |precision| { |
| | 1091 | // Round if needed. |
| | 1092 | if (precision < mantissa_digits) { |
| | 1093 | // We always have at least 4 extra bits. |
| | 1094 | var extra_bits = (mantissa_digits - precision) * 4; |
| | 1095 | // The result LSB is the Guard bit, we need two more (Round and |
| | 1096 | // Sticky) to round the value. |
| | 1097 | while (extra_bits > 2) { |
| | 1098 | mantissa = (mantissa >> 1) | (mantissa & 1); |
| | 1099 | extra_bits -= 1; |
| | 1100 | } |
| | 1101 | // Round to nearest, tie to even. |
| | 1102 | mantissa |= @boolToInt(mantissa & 0b100 != 0); |
| | 1103 | mantissa += 1; |
| | 1104 | // Drop the excess bits. |
| | 1105 | mantissa >>= 2; |
| | 1106 | // Restore the alignment. |
| | 1107 | mantissa <<= @intCast(math.Log2Int(TU), (mantissa_digits - precision) * 4); |
| | 1108 | |
| | 1109 | const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0; |
| | 1110 | // Prefer a normalized result in case of overflow. |
| | 1111 | if (overflow) { |
| | 1112 | mantissa >>= 1; |
| | 1113 | exponent += 1; |
| | 1114 | } |
| | 1115 | } |
| | 1116 | } |
| | 1117 | |
| | 1118 | // +1 for the decimal part. |
| | 1119 | var buf: [1 + mantissa_digits]u8 = undefined; |
| | 1120 | const N = formatIntBuf(&buf, mantissa, 16, false, .{ .fill = '0', .width = 1 + mantissa_digits }); |
| | 1121 | |
| | 1122 | try writer.writeAll("0x"); |
| | 1123 | try writer.writeByte(buf[0]); |
| | 1124 | if (options.precision != @as(usize, 0)) |
| | 1125 | try writer.writeAll("."); |
| | 1126 | const trimmed = mem.trimRight(u8, buf[1..], "0"); |
| | 1127 | try writer.writeAll(trimmed); |
| | 1128 | // Add trailing zeros if explicitly requested. |
| | 1129 | if (options.precision) |precision| if (precision > 0) { |
| | 1130 | if (precision > trimmed.len) |
| | 1131 | try writer.writeByteNTimes('0', precision - trimmed.len); |
| | 1132 | }; |
| | 1133 | try writer.writeAll("p"); |
| | 1134 | try formatInt(exponent - exponent_bias, 10, false, .{}, writer); |
| | 1135 | } |
| | 1136 | |
| 1026 | /// Print a float of the format x.yyyyy where the number of y is specified by the precision argument. | 1137 | /// Print a float of the format x.yyyyy where the number of y is specified by the precision argument. |
| 1027 | /// By default floats are printed at full precision (no rounding). | 1138 | /// By default floats are printed at full precision (no rounding). |
| 1028 | pub fn formatFloatDecimal( | 1139 | pub fn formatFloatDecimal( |
| ... | @@ -1900,6 +2011,54 @@ test "float.special" { | ... | @@ -1900,6 +2011,54 @@ test "float.special" { |
| 1900 | try expectFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); | 2011 | try expectFmt("f64: -inf", "f64: {}", .{-math.inf_f64}); |
| 1901 | } | 2012 | } |
| 1902 | | 2013 | |
| | 2014 | test "float.hexadecimal.special" { |
| | 2015 | try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64}); |
| | 2016 | // negative nan is not defined by IEE 754, |
| | 2017 | // and ARM thus normalizes it to positive nan |
| | 2018 | if (builtin.arch != builtin.Arch.arm) { |
| | 2019 | try expectFmt("f64: -nan", "f64: {x}", .{-math.nan_f64}); |
| | 2020 | } |
| | 2021 | try expectFmt("f64: inf", "f64: {x}", .{math.inf_f64}); |
| | 2022 | try expectFmt("f64: -inf", "f64: {x}", .{-math.inf_f64}); |
| | 2023 | |
| | 2024 | try expectFmt("f64: 0x0.0p0", "f64: {x}", .{@as(f64, 0)}); |
| | 2025 | try expectFmt("f64: -0x0.0p0", "f64: {x}", .{-@as(f64, 0)}); |
| | 2026 | } |
| | 2027 | |
| | 2028 | test "float.hexadecimal" { |
| | 2029 | try expectFmt("f16: 0x1.554p-2", "f16: {x}", .{@as(f16, 1.0 / 3.0)}); |
| | 2030 | try expectFmt("f32: 0x1.555556p-2", "f32: {x}", .{@as(f32, 1.0 / 3.0)}); |
| | 2031 | try expectFmt("f64: 0x1.5555555555555p-2", "f64: {x}", .{@as(f64, 1.0 / 3.0)}); |
| | 2032 | try expectFmt("f128: 0x1.5555555555555555555555555555p-2", "f128: {x}", .{@as(f128, 1.0 / 3.0)}); |
| | 2033 | |
| | 2034 | try expectFmt("f16: 0x1.p-14", "f16: {x}", .{@as(f16, math.f16_min)}); |
| | 2035 | try expectFmt("f32: 0x1.p-126", "f32: {x}", .{@as(f32, math.f32_min)}); |
| | 2036 | try expectFmt("f64: 0x1.p-1022", "f64: {x}", .{@as(f64, math.f64_min)}); |
| | 2037 | try expectFmt("f128: 0x1.p-16382", "f128: {x}", .{@as(f128, math.f128_min)}); |
| | 2038 | |
| | 2039 | try expectFmt("f16: 0x0.004p-14", "f16: {x}", .{@as(f16, math.f16_true_min)}); |
| | 2040 | try expectFmt("f32: 0x0.000002p-126", "f32: {x}", .{@as(f32, math.f32_true_min)}); |
| | 2041 | try expectFmt("f64: 0x0.0000000000001p-1022", "f64: {x}", .{@as(f64, math.f64_true_min)}); |
| | 2042 | try expectFmt("f128: 0x0.0000000000000000000000000001p-16382", "f128: {x}", .{@as(f128, math.f128_true_min)}); |
| | 2043 | |
| | 2044 | try expectFmt("f16: 0x1.ffcp15", "f16: {x}", .{@as(f16, math.f16_max)}); |
| | 2045 | try expectFmt("f32: 0x1.fffffep127", "f32: {x}", .{@as(f32, math.f32_max)}); |
| | 2046 | try expectFmt("f64: 0x1.fffffffffffffp1023", "f64: {x}", .{@as(f64, math.f64_max)}); |
| | 2047 | try expectFmt("f128: 0x1.ffffffffffffffffffffffffffffp16383", "f128: {x}", .{@as(f128, math.f128_max)}); |
| | 2048 | } |
| | 2049 | |
| | 2050 | test "float.hexadecimal.precision" { |
| | 2051 | try expectFmt("f16: 0x1.5p-2", "f16: {x:.1}", .{@as(f16, 1.0 / 3.0)}); |
| | 2052 | try expectFmt("f32: 0x1.555p-2", "f32: {x:.3}", .{@as(f32, 1.0 / 3.0)}); |
| | 2053 | try expectFmt("f64: 0x1.55555p-2", "f64: {x:.5}", .{@as(f64, 1.0 / 3.0)}); |
| | 2054 | try expectFmt("f128: 0x1.5555555p-2", "f128: {x:.7}", .{@as(f128, 1.0 / 3.0)}); |
| | 2055 | |
| | 2056 | try expectFmt("f16: 0x1.00000p0", "f16: {x:.5}", .{@as(f16, 1.0)}); |
| | 2057 | try expectFmt("f32: 0x1.00000p0", "f32: {x:.5}", .{@as(f32, 1.0)}); |
| | 2058 | try expectFmt("f64: 0x1.00000p0", "f64: {x:.5}", .{@as(f64, 1.0)}); |
| | 2059 | try expectFmt("f128: 0x1.00000p0", "f128: {x:.5}", .{@as(f128, 1.0)}); |
| | 2060 | } |
| | 2061 | |
| 1903 | test "float.decimal" { | 2062 | test "float.decimal" { |
| 1904 | try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); | 2063 | try expectFmt("f64: 152314000000000000000000000000", "f64: {d}", .{@as(f64, 1.52314e+29)}); |
| 1905 | try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); | 2064 | try expectFmt("f32: 0", "f32: {d}", .{@as(f32, 0.0)}); |