diff --git a/spec/std/sprintf_spec.cr b/spec/std/sprintf_spec.cr index 5a758d29551a..f37384d3a70c 100644 --- a/spec/std/sprintf_spec.cr +++ b/spec/std/sprintf_spec.cr @@ -1,4 +1,5 @@ require "./spec_helper" +require "../support/number" require "spec/helpers/string" require "big" @@ -581,13 +582,297 @@ describe "::sprintf" do end end - pending_win32 "works for other formats" do - assert_sprintf "%g", 123, "123" - assert_sprintf "%G", 12345678.45, "1.23457E+07" - assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" - assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" - assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" - assert_sprintf "%#.12g", 12345.0, "12345.0000000" + context "general format" do + pending_win32 "works" do + assert_sprintf "%g", 123, "123" + assert_sprintf "%G", 12345678.45, "1.23457E+07" + assert_sprintf "%100.50g", 123.45, " 123.4500000000000028421709430404007434844970703125" + assert_sprintf "%#.12g", 12345.0, "12345.0000000" + end + end + + context "hex format" do + it "works" do + assert_sprintf "%a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%A", 1194684.0, "0X1.23ABCP+20" + assert_sprintf "%a", 12345678.45, "0x1.78c29ce666666p+23" + assert_sprintf "%A", 12345678.45, "0X1.78C29CE666666P+23" + + assert_sprintf "%a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%a", Float64::MIN_POSITIVE, "0x1p-1022" + assert_sprintf "%a", Float64::MIN_SUBNORMAL, "0x0.0000000000001p-1022" + assert_sprintf "%a", 0.0, "0x0p+0" + assert_sprintf "%a", -0.0, "-0x0p+0" + assert_sprintf "%a", -Float64::MIN_SUBNORMAL, "-0x0.0000000000001p-1022" + assert_sprintf "%a", -Float64::MIN_POSITIVE, "-0x1p-1022" + assert_sprintf "%a", Float64::MIN, "-0x1.fffffffffffffp+1023" + end + + context "width specifier" do + it "sets the minimum length of the string" do + assert_sprintf "%20a", hexfloat("0x1p+0"), " 0x1p+0" + assert_sprintf "%20a", hexfloat("0x1.2p+0"), " 0x1.2p+0" + assert_sprintf "%20a", hexfloat("0x1.23p+0"), " 0x1.23p+0" + assert_sprintf "%20a", hexfloat("0x1.234p+0"), " 0x1.234p+0" + assert_sprintf "%20a", hexfloat("0x1.2345p+0"), " 0x1.2345p+0" + assert_sprintf "%20a", hexfloat("0x1.23456p+0"), " 0x1.23456p+0" + assert_sprintf "%20a", hexfloat("0x1.234567p+0"), " 0x1.234567p+0" + assert_sprintf "%20a", hexfloat("0x1.2345678p+0"), " 0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789p+0"), " 0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("0x1.23456789ap+0"), " 0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abp+0"), " 0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("0x1.23456789abcp+0"), " 0x1.23456789abcp+0" + + assert_sprintf "%20a", hexfloat("-0x1p+0"), " -0x1p+0" + assert_sprintf "%20a", hexfloat("-0x1.2p+0"), " -0x1.2p+0" + assert_sprintf "%20a", hexfloat("-0x1.23p+0"), " -0x1.23p+0" + assert_sprintf "%20a", hexfloat("-0x1.234p+0"), " -0x1.234p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345p+0"), " -0x1.2345p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456p+0"), " -0x1.23456p+0" + assert_sprintf "%20a", hexfloat("-0x1.234567p+0"), " -0x1.234567p+0" + assert_sprintf "%20a", hexfloat("-0x1.2345678p+0"), " -0x1.2345678p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789p+0"), " -0x1.23456789p+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789ap+0"), " -0x1.23456789ap+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abp+0"), " -0x1.23456789abp+0" + assert_sprintf "%20a", hexfloat("-0x1.23456789abcp+0"), " -0x1.23456789abcp+0" + + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + + assert_sprintf "%14a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "%14a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+14a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%13a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%13a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+13a", 1194684.0, "+0x1.23abcp+20" + + assert_sprintf "%2a", 1194684.0, "0x1.23abcp+20" + assert_sprintf "%2a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+2a", 1194684.0, "+0x1.23abcp+20" + end + + it "left-justifies on negative width" do + assert_sprintf "%*a", [-20, 1194684.0], "0x1.23abcp+20 " + end + end + + context "precision specifier" do + it "sets the minimum length of the fractional part" do + assert_sprintf "%.0a", 0.0, "0x0p+0" + + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).prev_float, "0x0p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE / 2, "0x0p-1022" + assert_sprintf "%.0a", (Float64::MIN_POSITIVE / 2).next_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE.prev_float, "0x1p-1022" + assert_sprintf "%.0a", Float64::MIN_POSITIVE, "0x1p-1022" + + assert_sprintf "%.0a", 0.0625, "0x1p-4" + assert_sprintf "%.0a", 0.0625.next_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375.prev_float, "0x1p-4" + assert_sprintf "%.0a", 0.09375, "0x2p-4" + assert_sprintf "%.0a", 0.09375.next_float, "0x2p-4" + assert_sprintf "%.0a", 0.125.prev_float, "0x2p-4" + assert_sprintf "%.0a", 0.125, "0x1p-3" + + assert_sprintf "%.1a", 2.0, "0x1.0p+1" + assert_sprintf "%.1a", 2.0.next_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.prev_float, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625, "0x1.0p+1" + assert_sprintf "%.1a", 2.0625.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.125, "0x1.1p+1" + assert_sprintf "%.1a", 2.125.next_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875.prev_float, "0x1.1p+1" + assert_sprintf "%.1a", 2.1875, "0x1.2p+1" + assert_sprintf "%.1a", 2.1875.next_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25.prev_float, "0x1.2p+1" + assert_sprintf "%.1a", 2.25, "0x1.2p+1" + + assert_sprintf "%.1a", 60.0, "0x1.ep+5" + assert_sprintf "%.1a", 60.0.next_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.prev_float, "0x1.ep+5" + assert_sprintf "%.1a", 61.0, "0x1.ep+5" + assert_sprintf "%.1a", 61.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 62.0, "0x1.fp+5" + assert_sprintf "%.1a", 62.0.next_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0.prev_float, "0x1.fp+5" + assert_sprintf "%.1a", 63.0, "0x2.0p+5" + assert_sprintf "%.1a", 63.0.next_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0.prev_float, "0x2.0p+5" + assert_sprintf "%.1a", 64.0, "0x1.0p+6" + + assert_sprintf "%.4a", 65536.0, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.0.next_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.prev_float, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5, "0x1.0000p+16" + assert_sprintf "%.4a", 65536.5.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.0.next_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5.prev_float, "0x1.0001p+16" + assert_sprintf "%.4a", 65537.5, "0x1.0002p+16" + assert_sprintf "%.4a", 65537.5.next_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0.prev_float, "0x1.0002p+16" + assert_sprintf "%.4a", 65538.0, "0x1.0002p+16" + + assert_sprintf "%.4a", 131070.0, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.0.next_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.prev_float, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5, "0x1.fffep+16" + assert_sprintf "%.4a", 131070.5.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.0.next_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5.prev_float, "0x1.ffffp+16" + assert_sprintf "%.4a", 131071.5, "0x2.0000p+16" + assert_sprintf "%.4a", 131071.5.next_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0.prev_float, "0x2.0000p+16" + assert_sprintf "%.4a", 131072.0, "0x1.0000p+17" + + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x01, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x07, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x08, "0x0.000000000000p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x09, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x0f, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x10, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x11, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x17, "0x0.000000000001p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x18, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x19, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x1f, "0x0.000000000002p-1022" + assert_sprintf "%.12a", Float64::MIN_SUBNORMAL * 0x20, "0x0.000000000002p-1022" + + assert_sprintf "%.17a", Float64::MAX, "0x1.fffffffffffff0000p+1023" + assert_sprintf "%.16a", Float64::MAX, "0x1.fffffffffffff000p+1023" + assert_sprintf "%.15a", Float64::MAX, "0x1.fffffffffffff00p+1023" + assert_sprintf "%.14a", Float64::MAX, "0x1.fffffffffffff0p+1023" + assert_sprintf "%.13a", Float64::MAX, "0x1.fffffffffffffp+1023" + assert_sprintf "%.12a", Float64::MAX, "0x2.000000000000p+1023" + assert_sprintf "%.11a", Float64::MAX, "0x2.00000000000p+1023" + assert_sprintf "%.10a", Float64::MAX, "0x2.0000000000p+1023" + assert_sprintf "%.9a", Float64::MAX, "0x2.000000000p+1023" + assert_sprintf "%.8a", Float64::MAX, "0x2.00000000p+1023" + assert_sprintf "%.7a", Float64::MAX, "0x2.0000000p+1023" + assert_sprintf "%.6a", Float64::MAX, "0x2.000000p+1023" + assert_sprintf "%.5a", Float64::MAX, "0x2.00000p+1023" + assert_sprintf "%.4a", Float64::MAX, "0x2.0000p+1023" + assert_sprintf "%.3a", Float64::MAX, "0x2.000p+1023" + assert_sprintf "%.2a", Float64::MAX, "0x2.00p+1023" + assert_sprintf "%.1a", Float64::MAX, "0x2.0p+1023" + assert_sprintf "%.0a", Float64::MAX, "0x2p+1023" + + assert_sprintf "%.1000a", 1194684.0, "0x1.23abc#{"0" * 995}p+20" + end + + it "can be used with width" do + assert_sprintf "%20.8a", 1194684.0, " 0x1.23abc000p+20" + assert_sprintf "%20.8a", -1194684.0, " -0x1.23abc000p+20" + assert_sprintf "%20.8a", 0.0, " 0x0.00000000p+0" + + assert_sprintf "%-20.8a", 1194684.0, "0x1.23abc000p+20 " + assert_sprintf "%-20.8a", -1194684.0, "-0x1.23abc000p+20 " + assert_sprintf "%-20.8a", 0.0, "0x0.00000000p+0 " + + assert_sprintf "%4.8a", 1194684.0, "0x1.23abc000p+20" + assert_sprintf "%4.8a", -1194684.0, "-0x1.23abc000p+20" + assert_sprintf "%4.8a", 0.0, "0x0.00000000p+0" + end + + it "is ignored if precision argument is negative" do + assert_sprintf "%.*a", [-2, 1194684.0], "0x1.23abcp+20" + end + end + + context "sharp flag" do + it "prints a decimal point even if no digits follow" do + assert_sprintf "%#a", 1.0, "0x1.p+0" + assert_sprintf "%#a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#a", 0.0, "0x0.p+0" + assert_sprintf "%#a", -0.0, "-0x0.p+0" + + assert_sprintf "%#.0a", 1.0, "0x1.p+0" + assert_sprintf "%#.0a", Float64::MIN_POSITIVE, "0x1.p-1022" + assert_sprintf "%#.0a", 2.0 ** -234, "0x1.p-234" + assert_sprintf "%#.0a", 2.0 ** 1021, "0x1.p+1021" + assert_sprintf "%#.0a", 1194684.0, "0x1.p+20" + assert_sprintf "%#.0a", 0.0, "0x0.p+0" + assert_sprintf "%#.0a", -0.0, "-0x0.p+0" + end + end + + context "plus flag" do + it "writes a plus sign for positive values" do + assert_sprintf "%+a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "%+a", 0.0, "+0x0p+0" + end + + it "writes plus sign after left space-padding" do + assert_sprintf "%+20a", 1194684.0, " +0x1.23abcp+20" + assert_sprintf "%+20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "%+20a", 0.0, " +0x0p+0" + end + + it "writes plus sign before left zero-padding" do + assert_sprintf "%+020a", 1194684.0, "+0x0000001.23abcp+20" + assert_sprintf "%+020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%+020a", 0.0, "+0x00000000000000p+0" + end + end + + context "space flag" do + it "writes a space for positive values" do + assert_sprintf "% a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% a", -1194684.0, "-0x1.23abcp+20" + assert_sprintf "% a", 0.0, " 0x0p+0" + end + + it "writes space before left space-padding" do + assert_sprintf "% 20a", 1194684.0, " 0x1.23abcp+20" + assert_sprintf "% 20a", -1194684.0, " -0x1.23abcp+20" + assert_sprintf "% 20a", 0.0, " 0x0p+0" + + assert_sprintf "% 020a", 1194684.0, " 0x0000001.23abcp+20" + assert_sprintf "% 020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "% 020a", 0.0, " 0x00000000000000p+0" + end + + it "is ignored if plus flag is also specified" do + assert_sprintf "% +a", 1194684.0, "+0x1.23abcp+20" + assert_sprintf "%+ a", -1194684.0, "-0x1.23abcp+20" + end + end + + context "zero flag" do + it "left-pads the result with zeros" do + assert_sprintf "%020a", 1194684.0, "0x00000001.23abcp+20" + assert_sprintf "%020a", -1194684.0, "-0x0000001.23abcp+20" + assert_sprintf "%020a", 0.0, "0x000000000000000p+0" + end + + it "is ignored if string is left-justified" do + assert_sprintf "%-020a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-020a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-020a", 0.0, "0x0p+0 " + end + + it "can be used with precision" do + assert_sprintf "%020.8a", 1194684.0, "0x00001.23abc000p+20" + assert_sprintf "%020.8a", -1194684.0, "-0x0001.23abc000p+20" + assert_sprintf "%020.8a", 0.0, "0x000000.00000000p+0" + end + end + + context "minus flag" do + it "left-justifies the string" do + assert_sprintf "%-20a", 1194684.0, "0x1.23abcp+20 " + assert_sprintf "%-20a", -1194684.0, "-0x1.23abcp+20 " + assert_sprintf "%-20a", 0.0, "0x0p+0 " + end + end end [Float32, Float64].each do |float| diff --git a/src/float/printer.cr b/src/float/printer.cr index 6552f74cdb4c..8fa64dd5072c 100644 --- a/src/float/printer.cr +++ b/src/float/printer.cr @@ -90,17 +90,17 @@ module Float::Printer # Writes *v*'s hexadecimal-significand representation to the given *io*. # - # Used by `Float::Primitive#to_hexfloat`. - def hexfloat(v : Float64, io : IO) : Nil + # Used by `Float::Primitive#to_hexfloat` and `String::Formatter#float_hex`. + def hexfloat(v : Float64, io : IO, **opts) : Nil check_finite_float(v, io) do - Hexfloat(Float64, UInt64).to_s(io, v) + Hexfloat(Float64, UInt64).to_s(io, v, **opts) end end # :ditto: - def hexfloat(v : Float32, io : IO) : Nil + def hexfloat(v : Float32, io : IO, **opts) : Nil check_finite_float(v, io) do - Hexfloat(Float32, UInt32).to_s(io, v) + Hexfloat(Float32, UInt32).to_s(io, v, **opts) end end diff --git a/src/float/printer/hexfloat.cr b/src/float/printer/hexfloat.cr index 1081211141c0..f08f4e53cd94 100644 --- a/src/float/printer/hexfloat.cr +++ b/src/float/printer/hexfloat.cr @@ -220,36 +220,97 @@ module Float::Printer::Hexfloat(F, U) # sign and special values are handled in `Float::Printer.check_finite_float` @[AlwaysInline] - def self.to_s(io : IO, num : F) : Nil + def self.to_s(io : IO, num : F, *, prefix : Bool = true, upcase : Bool = false, precision : Int? = nil, alternative : Bool = false) : Nil u = num.unsafe_as(U) exponent = ((u >> (F::MANT_DIGITS - 1)) & (F::MAX_EXP * 2 - 1)).to_i mantissa = u & ~(U::MAX << (F::MANT_DIGITS - 1)) if exponent < 1 exponent += 1 - io << "0x0" else - io << "0x1" + mantissa |= U.new!(1) << (F::MANT_DIGITS - 1) end - if mantissa != 0 - io << '.' + if precision + trailing_zeros = {(precision * 4 + 1 - F::MANT_DIGITS) // 4, 0}.max + precision -= trailing_zeros + + one_bit = mantissa.bits_set?(U.new!(1) << (F::MANT_DIGITS - 4 * precision - 1)) + half_bit = mantissa.bits_set?(U.new!(1) << (F::MANT_DIGITS - 4 * precision - 2)) + trailing_nonzero = (mantissa & ~(U::MAX << (F::MANT_DIGITS - 4 * precision - 2))) != 0 + if half_bit && (one_bit || trailing_nonzero) + mantissa &+= U.new!(1) << (F::MANT_DIGITS - 4 * precision - 2) + end + else + trailing_zeros = 0 + end + + io << (upcase ? "0X" : "0x") if prefix + io << (mantissa >> (F::MANT_DIGITS - 1)) + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + + io << '.' if (precision || mantissa) != 0 || alternative + + if precision + precision.times do + digit = mantissa >> (F::MANT_DIGITS - 5) + digit.to_s(io, base: 16, upcase: upcase) + mantissa <<= 4 + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + end + else while mantissa != 0 digit = mantissa >> (F::MANT_DIGITS - 5) - digit.to_s(io, base: 16) + digit.to_s(io, base: 16, upcase: upcase) mantissa <<= 4 mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) end end + trailing_zeros.times { io << '0' } + if num == 0 - io << "p+0" + io << (upcase ? "P+0" : "p+0") else exponent -= F::MAX_EXP - 1 - io << 'p' + io << (upcase ? 'P' : 'p') io << (exponent >= 0 ? '+' : '-') io << exponent.abs end end + + def self.to_s_size(num : F, *, precision : Int? = nil, alternative : Bool = false) + u = num.unsafe_as(U) + exponent = ((u >> (F::MANT_DIGITS - 1)) & (F::MAX_EXP * 2 - 1)).to_i + mantissa = u & ~(U::MAX << (F::MANT_DIGITS - 1)) + + if exponent < 1 + exponent += 1 + end + + size = 6 # 0x0p+0 (integral part cannot be greater than 2) + + if precision + size += 1 if precision != 0 || alternative # . + size += precision + else + size += 1 if mantissa != 0 || alternative # . + while mantissa != 0 + size += 1 + mantissa <<= 4 + mantissa &= ~(U::MAX << (F::MANT_DIGITS - 1)) + end + end + + if num != 0 + exponent = (exponent - F::MAX_EXP - 1).abs + while exponent >= 10 + exponent //= 10 + size += 1 + end + end + + size + end end diff --git a/src/string/formatter.cr b/src/string/formatter.cr index 4eb13ed79830..e85322f39d2e 100644 --- a/src/string/formatter.cr +++ b/src/string/formatter.cr @@ -247,7 +247,7 @@ struct String::Formatter(A) int = arg.is_a?(Int) ? arg : arg.to_i precision = int_precision(int, flags) - base_str = int.to_s(flags.base, precision: precision, upcase: flags.type == 'X') + base_str = int.to_s(flags.base, precision: precision, upcase: flags.uppercase?) str_size = base_str.bytesize str_size += 1 if int >= 0 && (flags.plus || flags.space) str_size += 2 if flags.sharp && flags.base != 10 && int != 0 @@ -334,7 +334,7 @@ struct String::Formatter(A) # Formats infinities and not-a-numbers private def float_special(str, sign, flags) - str = str.upcase if flags.type.in?('A', 'E', 'G') + str = str.upcase if flags.uppercase? str_size = str.bytesize str_size += 1 if sign < 0 || (flags.plus || flags.space) @@ -407,7 +407,7 @@ struct String::Formatter(A) e_index = printf_slice.rindex!('e'.ord) sign = Math.copysign(1.0, float) - printf_slice[e_index] = 'E'.ord.to_u8! if flags.type == 'E' + printf_slice[e_index] = 'E'.ord.to_u8! if flags.uppercase? str_size = printf_size + trailing_zeros str_size += 1 if sign < 0 || flags.plus || flags.space @@ -436,8 +436,25 @@ struct String::Formatter(A) # Formats floats with `%a` or `%A` private def float_hex(float, flags) - # TODO: implement using `Float::Printer::Hexfloat` - float_fallback(float, flags) + sign = Math.copysign(1.0, float) + float = float.abs + + str_size = Float::Printer::Hexfloat(Float64, UInt64).to_s_size(float, + precision: flags.precision, alternative: flags.sharp) + str_size += 1 if sign < 0 || flags.plus || flags.space + + pad(str_size, flags) if flags.left_padding? && flags.padding_char != '0' + + # this preserves -0.0's sign correctly + write_plus_or_space(sign, flags) + @io << '-' if sign < 0 + + @io << (flags.uppercase? ? "0X" : "0x") + pad(str_size, flags) if flags.left_padding? && flags.padding_char == '0' + Float::Printer.hexfloat(float, @io, + prefix: false, upcase: flags.uppercase?, precision: flags.precision, alternative: flags.sharp) + + pad(str_size, flags) if flags.right_padding? end {% end %} @@ -595,5 +612,9 @@ struct String::Formatter(A) def padding_char : Char @zero && !right_padding? && (@float || !@precision) ? '0' : ' ' end + + def uppercase? : Bool + @type.ascii_uppercase? + end end end