Skip to content

Commit

Permalink
Implement sprintf "%a" in Crystal (#14102)
Browse files Browse the repository at this point in the history
  • Loading branch information
HertzDevil authored Dec 17, 2023
1 parent 756a876 commit 8fe3c70
Show file tree
Hide file tree
Showing 4 changed files with 392 additions and 25 deletions.
299 changes: 292 additions & 7 deletions spec/std/sprintf_spec.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "./spec_helper"
require "../support/number"
require "spec/helpers/string"
require "big"

Expand Down Expand Up @@ -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|
Expand Down
10 changes: 5 additions & 5 deletions src/float/printer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit 8fe3c70

Please sign in to comment.