Skip to content

Hh/general numbers #52

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

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 9 additions & 3 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{T} where T, DefaultSpec}()

# adds a new default formatter for this type
default_spec!(::Type{T}, c::AbstractChar) where {T} =
Expand Down Expand Up @@ -79,6 +79,9 @@ default_spec(::Type{<:AbstractString}) = DEFAULT_FORMATTERS[AbstractString]
default_spec(::Type{<:AbstractChar}) = DEFAULT_FORMATTERS[AbstractChar]
default_spec(::Type{<:AbstractIrrational}) = DEFAULT_FORMATTERS[AbstractIrrational]
default_spec(::Type{<:Number}) = DEFAULT_FORMATTERS[Number]
default_spec(::Complex{T} where T<:Integer) = DEFAULT_FORMATTERS[Complex{T} where T<:Integer]
Copy link
Member

@ScottPJones ScottPJones Dec 17, 2019

Choose a reason for hiding this comment

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

This should have Type{<:Complex} to handle complex numbers that use other numeric types as a fallback.
I still don't think you should have specific defaults for more specific types, but if so, they should be written as following:
These should be Type{Complex{<:Integer}}, Type{Complex{<:AbstractFloat}}, Type{Complex{<:Rational}}

Copy link
Author

Choose a reason for hiding this comment

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

  • I had tried exactly that before, but I received
julia> hh(x::Type{Complex{<:Integer}}) = show(typeof(x))

hh (generic function with 1 method)

julia> hh(2 + 3im)
ERROR: MethodError: no method matching hh(::Complex{Int64})
Closest candidates are:
  hh(::Type{Complex{#s12} where #s12<:Integer}) at REPL[114]:1
Stacktrace:
 [1] top-level scope at REPL[115]:1

julia> gg(x::Complex{<:Integer}) = show(typeof(x))
gg (generic function with 1 method)

julia> gg(2 + 3im)
Complex{Int64}

So I accepted that Type{Complex{<:Integer}} is a type of a type, which is what we don't want ...
I am happy to accept any prettier solution.

  • In much the same way, I think, the fallback should be <:Complex instead of Type{<:Complex}
julia> typeof(2 + im) <: Type{<:Complex}
false

julia> typeof(2 + im) <: Complex
true

But I consider myself still a beginner in type programming, so please fell free to jump in, if you have a better way of putting it.

  • I wrote Complex{T} where T<:Integer because, strangely the Dict does not test on identity before adding a new type:
julia> d = Dict{Type{T} where T, String}()
Dict{Type,String} with 0 entries

julia> d[Complex{T}  where T <: Integer] = "test"
"test"

julia> d[Complex{S}  where S <: Integer] = "test"
"test"

julia> d[Complex{<:Integer}] = "test"
"test"

julia> d
Dict{Type,String} with 3 entries:
  Complex{#s15} where #s15<:Integer => "test"
  Complex{T} where T<:Integer       => "test"
  Complex{S} where S<:Integer       => "test"

julia> (Complex{T} where T<:Integer) == (Complex{S} where S<:Integer)
true

So default_fmt!(Complex{<:Integer}, 's') will just add another entry to the dict instead of overwriting. That is also the reason, why - in the first run - I chose to define the type ComplexInteger = Complex{T} where T<:Integer.

default_spec(::Complex{T} where T<:AbstractFloat) = DEFAULT_FORMATTERS[Complex{T} where T<:AbstractFloat]
default_spec(::Complex{T} where T<:Rational) = DEFAULT_FORMATTERS[Complex{T} where T<:Rational]

default_spec(::Type{T}) where {T} =
get(DEFAULT_FORMATTERS, T) do
Expand Down Expand Up @@ -189,7 +192,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 +223,12 @@ end
for (t, c) in [(Integer,'d'),
(AbstractFloat,'f'),
(AbstractChar,'c'),
(AbstractString,'s')]
(AbstractString,'s'),
(Complex{T} where T<:Integer, 'd' ),
(Complex{T} where T<:AbstractFloat, 'f' )]
default_spec!(t, c)
end

default_spec!(Number, 's', :right)
default_spec!(AbstractIrrational, 's', :right)
default_spec!(Complex{T} where T<:Rational, 's', :right)
38 changes: 38 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,40 @@ 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::AbstractFloat->begin
io = IOBuffer()
_pf(io, fsi, x)
String(take!(io))
end
s = fmt_Number(x, f)
_pfmt_s(out, fs, 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::Integer->begin
io = IOBuffer()
_pf(io, fsi, x, op)
String(take!(io))
end
s = fmt_Number(x, f)
_pfmt_s(out, fs, 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
15 changes: 13 additions & 2 deletions src/fmtspec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,19 +164,30 @@ _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)
ix = x
try
ix = Integer(x)
catch
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)
fx = x
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
end
if isfinite(fx)
ty == 'f' || ty == 'F' ? _pfmt_f(io, fs, fx) :
ty == 'e' || ty == 'E' ? _pfmt_e(io, fs, fx) :
Expand Down
21 changes: 19 additions & 2 deletions test/fmt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,19 @@ 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)
@test fmt(2 - 3im, 10) == " 2 - 3im"
@test fmt(pi - 3im, 15, 2) == " 3.14 - 3.00im"

@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, 2) == " 0.75"
@test fmt(3//4, 10, 1) == " 0.8"
@test fmt(1//2 + 6//2 * im, 23) == " 0.500000 + 3.000000im"
@test fmt(1//2 + 6//2 * im, 15, 2) == " 0.50 + 3.00im"

fmt_default!(Int, :commas, width = 12)
@test fmt(i) == " 1,234,567"
Expand All @@ -41,3 +52,9 @@ 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) == " γ"
14 changes: 14 additions & 0 deletions test/fmtspec.jl
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,17 @@ end
@test pyfmt("*>5f", Inf) == "**Inf"
@test pyfmt("⋆>5f", Inf) == "⋆⋆Inf"
end

@testset "Format Symbols (S) for 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 Symbols (S) for Irrationals" begin
pyfmt("10s", 3//4) == "3//4 "
pyfmt("10", 3//4) == " 3//4"
pyfmt("10.1f", 3//4) == " 0.8"
end