Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hh/general numbers #52

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ keywords = ["Strings", "Formatting"]
license = "MIT"
name = "Format"
uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
version = "1.1.0"
version = "1.2.0"

[deps]
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Expand Down
17 changes: 16 additions & 1 deletion src/cformat.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
formatters = Dict{ ASCIIStr, Function }()

cfmt( fmt::ASCIIStr, x ) = m_eval(Expr(:call, generate_formatter( fmt ), x))
cfmt( fmt::ASCIIStr, x::Union{<:AbstractString,<:Real,<:Rational,<:Char} ) = m_eval(Expr(:call, generate_formatter( fmt ), x))

function cfmt( fmt_str::ASCIIStr, x::Number )
#remove width information
new_fmt_str = replace(fmt_str, r"(%(\d+\$)?[\-\+#0' ]*)(\d+)?"=>s"\1")
s = fmt_Number(x, x->m_eval(Expr(:call, generate_formatter( new_fmt_str ), AbstractFloat(x))))
# extract width information
m = match(r"%(\d+\$)?[\-\+#0' ]*(\d+)?", fmt_str)
width = m[2] == nothing ? 0 : parse(Int, m[2])
fmt(s, width, occursin("-", fmt_str) ? :left : :right)
end

function checkfmt(fmt)
test = PF.parse( fmt )
Expand Down Expand Up @@ -117,6 +127,11 @@ function generate_format_string(;
String(append!(s, _codeunits(conversion)))
end

function format(x::T; kwargs...) where T<:Number
s = fmt_Number(x, x->format(AbstractFloat(x); kwargs..., width=-1))
fmt(s, get(kwargs, :width, 0), get(kwargs, :leftjustified, false) ? :left : :right)
end

function format( x::T;
width::Int=-1,
precision::Int= -1,
Expand Down
21 changes: 17 additions & 4 deletions src/fmt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function DefaultSpec(c::AbstractChar, syms...; kwargs...)
end
end

const DEFAULT_FORMATTERS = Dict{DataType, DefaultSpec}()
const DEFAULT_FORMATTERS = Dict{Type{<:Any}, DefaultSpec}()

# adds a new default formatter for this type
default_spec!(::Type{T}, c::AbstractChar) where {T} =
Expand Down Expand Up @@ -73,12 +73,21 @@ end
# methods to get the current default objects
# note: if you want to set a default for an abstract type (i.e. AbstractFloat)
# you'll need to extend this method like here:

const ComplexInteger = Complex{T} where T<:Integer
const ComplexFloat = Complex{T} where T<:AbstractFloat
const ComplexRational = Complex{T} where T<:Rational

default_spec(::Type{<:Integer}) = DEFAULT_FORMATTERS[Integer]
default_spec(::Type{<:AbstractFloat}) = DEFAULT_FORMATTERS[AbstractFloat]
default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString]
default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar]
default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational]
default_spec(::Type{<:Rational}) = DEFAULT_FORMATTERS[Rational]
default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number]
default_spec(::Type{<:ComplexInteger}) = DEFAULT_FORMATTERS[ComplexInteger]
default_spec(::Type{<:ComplexFloat}) = DEFAULT_FORMATTERS[ComplexFloat]
default_spec(::Type{<:ComplexRational}) = DEFAULT_FORMATTERS[ComplexRational]

default_spec(::Type{T}) where {T} =
get(DEFAULT_FORMATTERS, T) do
Expand Down Expand Up @@ -189,7 +198,7 @@ function fmt end
# TODO: do more caching to optimize repeated calls

# creates a new FormatSpec by overriding the defaults and passes it to pyfmt
# note: adding kwargs is only appropriate for one-off formatting.
# note: adding kwargs is only appropriate for one-off formatting.
# normally it will be much faster to change the fmt_default formatting as needed
function fmt(x; kwargs...)
fspec = fmt_default(x)
Expand Down Expand Up @@ -220,9 +229,13 @@ end
for (t, c) in [(Integer,'d'),
(AbstractFloat,'f'),
(AbstractChar,'c'),
(AbstractString,'s')]
(AbstractString,'s'),
(ComplexInteger,'d'),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, please remove the separate ComplexInteger, ComplexFloat, ComplexRational default specs.

(ComplexFloat,'f')]
default_spec!(t, c)
end

default_spec!(Number, 's', :right)
default_spec!(Rational, 's', :right)
default_spec!(AbstractIrrational, 's', :right)
default_spec!(ComplexRational, 's', :right)
default_spec!(Number, 's', :right)
40 changes: 40 additions & 0 deletions src/fmtcore.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# core formatting functions
export fmt_Number

### auxiliary functions

Expand Down Expand Up @@ -262,3 +263,42 @@ function _pfmt_specialf(out::IO, fs::FormatSpec, x::AbstractFloat)
end
end

function _pfmt_Number_f(out::IO, fs::FormatSpec, x::Number, _pf::Function)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These still have the issues that I mentioned before.

fsi = FormatSpec(fs, width = -1)
f = x->begin
fx = AbstractFloat(x) # not float(x), this should error out, if conversion is not possible
io = IOBuffer()
_pf(io, fsi, fx)
String(take!(io))
end
s = fmt_Number(x, f)
_pfmt_s(out, FormatSpec(fs, tsep = false), s)
end

function _pfmt_Number_i(out::IO, fs::FormatSpec, x::Number, op::Op, _pf::Function) where {Op}
fsi = FormatSpec(fs, width = -1)
f = x->begin
ix = Integer(x)
io = IOBuffer()
_pf(io, fsi, ix, op)
String(take!(io))
end
s = fmt_Number(x, f)
_pfmt_s(out, FormatSpec(fs, tsep = false), s)
end

function _pfmt_i(out::IO, fs::FormatSpec, x::Number, op::Op) where {Op}
_pfmt_Number_i(out, fs, x, op, _pfmt_i)
end

function _pfmt_f(out::IO, fs::FormatSpec, x::Number)
_pfmt_Number_f(out, fs, x, _pfmt_f)
end

function _pfmt_e(out::IO, fs::FormatSpec, x::Number)
_pfmt_Number_f(out, fs, x, _pfmt_e)
end

function fmt_Number(x::Complex, f::Function)
s = f(real(x)) * (imag(x) >= 0 ? " + " : " - ") * f(abs(imag(x))) * "im"
end
24 changes: 21 additions & 3 deletions src/fmtspec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,20 +164,38 @@ _srepr(x) = repr(x)
_srepr(x::AbstractString) = x
_srepr(x::AbstractChar) = string(x)
_srepr(x::Enum) = string(x)
@static if VERSION < v"1.2.0-DEV"
_srepr(x::Irrational{sym}) where {sym} = string(sym)
end

function printfmt(io::IO, fs::FormatSpec, x)
cls = fs.cls
ty = fs.typ
if cls == 'i'
ix = Integer(x)
local ix
try
ix = Integer(x)
catch
ix = x
end
ty == 'd' || ty == 'n' ? _pfmt_i(io, fs, ix, _Dec()) :
ty == 'x' ? _pfmt_i(io, fs, ix, _Hex()) :
ty == 'X' ? _pfmt_i(io, fs, ix, _HEX()) :
ty == 'o' ? _pfmt_i(io, fs, ix, _Oct()) :
_pfmt_i(io, fs, ix, _Bin())
elseif cls == 'f'
fx = float(x)
if isfinite(fx)
local fx, nospecialf
try
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - if it can't get converted to something that is AbstractFloat, then it should output as string, or give an error.

fx = float(x)
catch
fx = x
end
try
nospecialf = isfinite(fx)
catch
nospecialf = true
end
if nospecialf
ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) :
ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) :
error("format for type g or G is not supported yet (use f or e instead).")
Expand Down
1 change: 1 addition & 0 deletions src/formatexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ function printfmt(io::IO, fe::FormatExpr, args...)
end
end
isempty(fe.suffix) || print(io, fe.suffix)
nothing
end

const StringOrFE = Union{AbstractString, FormatExpr}
Expand Down
16 changes: 16 additions & 0 deletions test/cformat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,19 @@ end
@test format( 100, precision=2, suffix="%", conversion="f" ) == "100.00%"
end

@testset "complex numbers" begin
c = 2 - 3.1im
@test cfmt("%20.0f", c) == " 2 - 3im"
@test cfmt("%20.1f", c) == " 2.0 - 3.1im"
@test cfmt("%20.2f", c) == " 2.00 - 3.10im"
@test cfmt("%20s", c) == " 2.0 - 3.1im"
@test cfmt("%20.1e", c) == " 2.0e+00 - 3.1e+00im"
@test cfmt("%-20.1e", c) == "2.0e+00 - 3.1e+00im "

@test format(c, width=20) == " 2 - 3.1im"
@test format(c, width=20, precision=0) == " 2 - 3im"
@test format(c, width=20, precision=1) == " 2.0 - 3.1im"
@test format(c, width=20, precision=2) == " 2.00 - 3.10im"
@test format(c, width=20, precision=2, leftjustified=true) == "2.00 - 3.10im "
@test format(c, width=20, precision=1, conversion="e") == " 2.0e+00 - 3.1e+00im"
end
30 changes: 28 additions & 2 deletions test/fmt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ i = 1234567
@test fmt(i) == "1234567"
@test fmt(i,:commas) == "1,234,567"

@test_throws ErrorException fmt_default(Real)
@test_throws ErrorException fmt_default(Complex)
# These are not handled
#@test_throws ErrorException fmt_default(Real)
#@test_throws ErrorException fmt_default(Complex)

fmt_default!(Int, :commas, width = 12)
@test fmt(i) == " 1,234,567"
Expand All @@ -41,3 +42,28 @@ fmt_default!(UInt16, 'd', :commas)
fmt_default!(UInt32, UInt16, width=20)
@test fmt(0xfffff) == " 1,048,575"

v = pi
@test fmt(v) == "π"
@test fmt(v; width=10) == " π"

v = MathConstants.eulergamma
@test fmt(v, 10, 2) == " γ"

reset!(Number)
reset!(Real)
@test fmt_default(Real) == fmt_default(Number) == FormatSpec('s', align = '>')

@test fmt(2 - 3im, 10) == " 2 - 3im"
@test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im"

reset!(Rational)
@test fmt(3//4, 10) == " 3//4"
@test fmt(1//2 + 6//2 * im, 15) == " 1//2 + 3//1*im"

fmt_default!(Rational, 'f', prec = 2)
fmt_default!(Format.ComplexRational, 'f', prec = 2)

@test fmt(3//4, 10) == " 0.75"
@test fmt(3//4, 10, 1) == " 0.8"
@test fmt(1//2 + 6//2 * im, 15) == " 0.50 + 3.00im"
@test fmt(1//2 + 6//2 * im, 15, 1) == " 0.5 + 3.0im"
37 changes: 37 additions & 0 deletions test/fmtspec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,40 @@ end
@test pyfmt("*>5f", Inf) == "**Inf"
@test pyfmt("⋆>5f", Inf) == "⋆⋆Inf"
end

@testset "Format Irrationals" begin
@test pyfmt(">10s", pi) == " π"
@test pyfmt("10s", pi) == "π "
@test pyfmt("3", MathConstants.eulergamma) == "γ "
@test pyfmt("10.2f", MathConstants.eulergamma) == " 0.58"
@test pyfmt("<3s", MathConstants.e) == "ℯ "
end

@testset "Format Rationals" begin
@test pyfmt("10s", 3//4) == "3//4 "
@test pyfmt("10", 3//4) == "3//4 "
@test pyfmt(">10", 3//4) == " 3//4"
@test pyfmt("10.1f", 3//4) == " 0.8"
@test pyfmt("10.1f", 3//4) == " 0.8"
@test pyfmt("10.1e", 3//4) == " 7.5e-01"
end

@testset "Format Complex Numbers" begin
c = 2 - 3.1im
@test fmt(round(c), 20, 1) == " 2.0 - 3.0im"
@test fmt(c, 20, 1) == " 2.0 - 3.1im"
@test fmt(c, 20, 2) == " 2.00 - 3.10im"
@test fmt(c, 20) == "2.000000 - 3.100000im"
fmt_default!(Format.ComplexFloat, 'e')
@test fmt(c, 20, 1) == " 2.0e+00 - 3.1e+00im"
fmt_default!(Format.ComplexFloat, 'e', :left)
@test fmt(c, 20, 1) == "2.0e+00 - 3.1e+00im "

@test format(c, width=20) == " 2 - 3.1im"
@test format(c, width=20, precision=0) == " 2 - 3im"
@test format(c, width=20, precision=1) == " 2.0 - 3.1im"
@test format(c, width=20, precision=2) == " 2.00 - 3.10im"
@test format(c, width=20, precision=2, leftjustified=true) == "2.00 - 3.10im "
@test format(c, width=20, precision=1, conversion="e") == " 2.0e+00 - 3.1e+00im"
fmt_default!(Format.ComplexFloat, 'f')
end