Skip to content

Commit

Permalink
add parse(Complex{T}, s) (JuliaLang#24713)
Browse files Browse the repository at this point in the history
  • Loading branch information
stevengj authored and evetion committed Dec 12, 2017
1 parent 8a3b742 commit 627f0d3
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 39 deletions.
2 changes: 2 additions & 0 deletions NEWS.md
Expand Up @@ -353,6 +353,8 @@ Library improvements
* The function `randn` now accepts complex arguments (`Complex{T <: AbstractFloat}`)
([#21973]).

* `parse(Complex{T}, string)` can parse complex numbers in common formats ([#24713]).

* The function `rand` can now pick up random elements from strings, associatives
and sets ([#22228], [#21960], [#18155], [#22224]).

Expand Down
1 change: 1 addition & 0 deletions base/mpfr.jl
Expand Up @@ -125,6 +125,7 @@ convert(::Type{BigFloat}, x::Union{Float16,Float32}) = BigFloat(Float64(x))
convert(::Type{BigFloat}, x::Rational) = BigFloat(numerator(x)) / BigFloat(denominator(x))

function tryparse(::Type{BigFloat}, s::AbstractString, base::Int=0)
!isempty(s) && isspace(s[end]) && return tryparse(BigFloat, rstrip(s), base)
z = BigFloat()
err = ccall((:mpfr_set_str, :libmpfr), Int32, (Ref{BigFloat}, Cstring, Int32, Int32), z, s, base, ROUNDING_MODE[])
err == 0 ? Nullable(z) : Nullable{BigFloat}()
Expand Down
100 changes: 90 additions & 10 deletions base/parse.jl
Expand Up @@ -7,9 +7,12 @@ import Base.Checked: add_with_overflow, mul_with_overflow
"""
parse(type, str, [base])
Parse a string as a number. If the type is an integer type, then a base can be specified
(the default is 10). If the type is a floating point type, the string is parsed as a decimal
floating point number. If the string does not contain a valid number, an error is raised.
Parse a string as a number. For `Integer` types, a base can be specified
(the default is 10). For floating-point types, the string is parsed as a decimal
floating-point number. `Complex` types are parsed from decimal strings
of the form `"R±Iim"` as a `Complex(R,I)` of the requested type; `"i"` or `"j"` can also be
used instead of `"im"`, and `"R"` or `"Iim"` are also permitted.
If the string does not contain a valid number, an error is raised.
```jldoctest
julia> parse(Int, "1234")
Expand All @@ -23,6 +26,9 @@ julia> parse(Int, "afc", 16)
julia> parse(Float64, "1.2e-3")
0.0012
julia> parse(Complex{Float64}, "3.2e-1 + 4.5im")
0.32 + 4.5im
```
"""
parse(T::Type, str, base=Int)
Expand Down Expand Up @@ -151,7 +157,7 @@ function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::
return Nullable{T}(n)
end

function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString},
function tryparse_internal(::Type{Bool}, sbuff::Union{String,SubString{String}},
startpos::Int, endpos::Int, base::Integer, raise::Bool)
if isempty(sbuff)
raise && throw(ArgumentError("input string is empty"))
Expand Down Expand Up @@ -215,24 +221,98 @@ function parse(::Type{T}, s::AbstractString) where T<:Integer
get(tryparse_internal(T, s, start(s), endof(s), 0, true)) # Zero means, "figure it out"
end


## string to float functions ##

tryparse(::Type{Float64}, s::String) = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8},Csize_t,Csize_t), s, 0, sizeof(s))
tryparse(::Type{Float64}, s::SubString{String}) = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset, s.endof)
tryparse_internal(::Type{Float64}, s::String, startpos::Int, endpos::Int) = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8},Csize_t,Csize_t), s, startpos-1, endpos-startpos+1)
tryparse_internal(::Type{Float64}, s::SubString{String}, startpos::Int, endpos::Int) = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset+startpos-1, endpos-startpos+1)

tryparse(::Type{Float32}, s::String) = ccall(:jl_try_substrtof, Nullable{Float32}, (Ptr{UInt8},Csize_t,Csize_t), s, 0, sizeof(s))
tryparse(::Type{Float32}, s::SubString{String}) = ccall(:jl_try_substrtof, Nullable{Float32}, (Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset, s.endof)
tryparse_internal(::Type{Float32}, s::String, startpos::Int, endpos::Int) = ccall(:jl_try_substrtof, Nullable{Float32}, (Ptr{UInt8},Csize_t,Csize_t), s, startpos-1, endpos-startpos+1)
tryparse_internal(::Type{Float32}, s::SubString{String}, startpos::Int, endpos::Int) = ccall(:jl_try_substrtof, Nullable{Float32}, (Ptr{UInt8},Csize_t,Csize_t), s.string, s.offset+startpos-1, endpos-startpos+1)

tryparse(::Type{T}, s::AbstractString) where {T<:Union{Float32,Float64}} = tryparse(T, String(s))

tryparse(::Type{Float16}, s::AbstractString) = convert(Nullable{Float16}, tryparse(Float32, s))
tryparse_internal(::Type{Float16}, s::AbstractString, startpos::Int, endpos::Int) =
convert(Nullable{Float16}, tryparse_internal(Float32, s, startpos, endpos))

## string to complex functions ##

function tryparse_internal(::Type{Complex{T}}, s::Union{String,SubString{String}}, i::Int, e::Int, raise::Bool) where {T<:Real}
# skip initial whitespace
while i e && isspace(s[i])
i = nextind(s, i)
end
if i > e
raise && throw(ArgumentError("input string is empty or only contains whitespace"))
return Nullable{Complex{T}}()
end

# find index of ± separating real/imaginary parts (if any)
i₊ = search(s, ('+','-'), i)
if i₊ == i # leading ± sign
i₊ = search(s, ('+','-'), i₊+1)
end
if i₊ != 0 && s[i₊-1] in ('e','E') # exponent sign
i₊ = search(s, ('+','-'), i₊+1)
end

# find trailing im/i/j
iᵢ = rsearch(s, ('m','i','j'), e)
if iᵢ > 0 && s[iᵢ] == 'm' # im
iᵢ -= 1
if s[iᵢ] != 'i'
raise && throw(ArgumentError("expected trailing \"im\", found only \"m\""))
return Nullable{Complex{T}}()
end
end

if i₊ == 0 # purely real or imaginary value
if iᵢ > 0 # purely imaginary
x_ = tryparse_internal(T, s, i, iᵢ-1, raise)
isnull(x_) && return Nullable{Complex{T}}()
x = unsafe_get(x_)
return Nullable{Complex{T}}(Complex{T}(zero(x),x))
else # purely real
return Nullable{Complex{T}}(tryparse_internal(T, s, i, e, raise))
end
end

if iᵢ < i₊
raise && throw(ArgumentError("missing imaginary unit"))
return Nullable{Complex{T}}() # no imaginary part
end

# parse real part
re = tryparse_internal(T, s, i, i₊-1, raise)
isnull(re) && return Nullable{Complex{T}}()

# parse imaginary part
im = tryparse_internal(T, s, i₊+1, iᵢ-1, raise)
isnull(im) && return Nullable{Complex{T}}()

return Nullable{Complex{T}}(Complex{T}(unsafe_get(re), s[i₊]=='-' ? -unsafe_get(im) : unsafe_get(im)))
end

function parse(::Type{T}, s::AbstractString) where T<:AbstractFloat
result = tryparse(T, s)
if isnull(result)
throw(ArgumentError("cannot parse $(repr(s)) as $T"))
# the ±1 indexing above for ascii chars is specific to String, so convert:
tryparse_internal(T::Type{<:Complex}, s::AbstractString, i::Int, e::Int, raise::Bool) =
tryparse_internal(T, String(s), i, e, raise)

# fallback methods for tryparse_internal
tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int) where T<:Real =
startpos == start(s) && endpos == endof(s) ? tryparse(T, s) : tryparse(T, SubString(s, startpos, endpos))
function tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, raise::Bool) where T<:Real
result = tryparse_internal(T, s, startpos, endpos)
if raise && isnull(result)
throw(ArgumentError("cannot parse $(repr(s[startpos:endpos])) as $T"))
end
return unsafe_get(result)
return result
end
tryparse_internal(::Type{T}, s::AbstractString, startpos::Int, endpos::Int, raise::Bool) where T<:Integer =
tryparse_internal(T, s, startpos, endpos, 10, raise)

parse(::Type{T}, s::AbstractString) where T<:Union{Real,Complex} =
unsafe_get(tryparse_internal(T, s, start(s), endof(s), true))
21 changes: 8 additions & 13 deletions stdlib/DelimitedFiles/src/DelimitedFiles.jl
Expand Up @@ -393,22 +393,17 @@ end

function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Bool,2}, row::Int, col::Int)
n = tryparse_internal(Bool, sbuff, startpos, endpos, 0, false)
isnull(n) || (cells[row, col] = get(n))
isnull(n) || (cells[row, col] = unsafe_get(n))
isnull(n)
end
function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{T,2}, row::Int, col::Int) where T<:Integer
n = tryparse_internal(T, sbuff, startpos, endpos, 0, false)
isnull(n) || (cells[row, col] = get(n))
isnull(n) || (cells[row, col] = unsafe_get(n))
isnull(n)
end
function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Float64,2}, row::Int, col::Int)
n = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8},Csize_t,Csize_t), sbuff, startpos-1, endpos-startpos+1)
isnull(n) || (cells[row, col] = get(n))
isnull(n)
end
function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Float32,2}, row::Int, col::Int)
n = ccall(:jl_try_substrtof, Nullable{Float32}, (Ptr{UInt8}, Csize_t, Csize_t), sbuff, startpos-1, endpos-startpos+1)
isnull(n) || (cells[row, col] = get(n))
function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{T,2}, row::Int, col::Int) where T<:Union{Real,Complex}
n = tryparse_internal(T, sbuff, startpos, endpos, false)
isnull(n) || (cells[row, col] = unsafe_get(n))
isnull(n)
end
function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{<:AbstractString,2}, row::Int, col::Int)
Expand All @@ -421,15 +416,15 @@ function colval(sbuff::String, startpos::Int, endpos::Int, cells::Array{Any,2},
if len > 0
# check Inteter
ni64 = tryparse_internal(Int, sbuff, startpos, endpos, 0, false)
isnull(ni64) || (cells[row, col] = get(ni64); return false)
isnull(ni64) || (cells[row, col] = unsafe_get(ni64); return false)

# check Bool
nb = tryparse_internal(Bool, sbuff, startpos, endpos, 0, false)
isnull(nb) || (cells[row, col] = get(nb); return false)
isnull(nb) || (cells[row, col] = unsafe_get(nb); return false)

# check float64
nf64 = ccall(:jl_try_substrtod, Nullable{Float64}, (Ptr{UInt8}, Csize_t, Csize_t), sbuff, startpos-1, endpos-startpos+1)
isnull(nf64) || (cells[row, col] = get(nf64); return false)
isnull(nf64) || (cells[row, col] = unsafe_get(nf64); return false)
end
cells[row, col] = SubString(sbuff, startpos, endpos)
false
Expand Down
4 changes: 4 additions & 0 deletions stdlib/DelimitedFiles/test/runtests.jl
Expand Up @@ -290,3 +290,7 @@ let d = TextDisplay(IOBuffer())
display(d, "text/csv", [3 1 4])
@test String(take!(d.io)) == "3,1,4\n"
end

@testset "complex" begin
@test readdlm(IOBuffer("3+4im, 4+5im"), ',', Complex{Int}) == [3+4im 4+5im]
end
20 changes: 4 additions & 16 deletions test/mpfr.jl
Expand Up @@ -7,22 +7,10 @@ import Base.MPFR
x = BigFloat(12)
end
x = BigFloat(12)
y = BigFloat(x)
@test x y
y = BigFloat(0xc)
@test x y
y = BigFloat(12.)
@test x y
y = BigFloat(BigInt(12))
@test x y
y = BigFloat(BigFloat(12))
@test x y
y = parse(BigFloat,"12")
@test x y
y = BigFloat(Float32(12.))
@test x y
y = BigFloat(12//1)
@test x y
@test x == BigFloat(x) == BigFloat(0xc) == BigFloat(12.) ==
BigFloat(BigInt(12)) == BigFloat(BigFloat(12)) == parse(BigFloat,"12") ==
parse(BigFloat,"12 ") == parse(BigFloat," 12") == parse(BigFloat," 12 ") ==
BigFloat(Float32(12.)) == BigFloat(12//1)

@test typeof(BigFloat(typemax(Int8))) == BigFloat
@test typeof(BigFloat(typemax(Int16))) == BigFloat
Expand Down
25 changes: 25 additions & 0 deletions test/parse.jl
Expand Up @@ -229,3 +229,28 @@ end
@test tryparse(Float32, "1.23") === Nullable(1.23f0)
@test tryparse(Float16, "1.23") === Nullable(Float16(1.23))

# parsing complex numbers (#22250)
@testset "complex parsing" begin
for r in (1,0,-1), i in (1,0,-1), sign in ('-','+'), Im in ("i","j","im")
for s1 in (""," "), s2 in (""," "), s3 in (""," "), s4 in (""," ")
n = Complex(r, sign == '+' ? i : -i)
s = string(s1, r, s2, sign, s3, i, Im, s4)
@test n === parse(Complex{Int}, s)
@test Complex(r) === parse(Complex{Int}, string(s1, r, s2))
@test Complex(0,i) === parse(Complex{Int}, string(s3, i, Im, s4))
for T in (Float64, BigFloat)
nT = parse(Complex{T}, s)
@test nT isa Complex{T}
@test nT == n
@test n == parse(Complex{T}, string(s1, r, ".0", s2, sign, s3, i, ".0", Im, s4))
@test n*parse(T,"1e-3") == parse(Complex{T}, string(s1, r, "e-3", s2, sign, s3, i, "e-3", Im, s4))
end
end
end
@test parse(Complex{Float16}, "3.3+4i") === Complex{Float16}(3.3+4im)
@test parse(Complex{Int}, SubString("xxxxxx1+2imxxxx", 7, 10)) === 1+2im
for T in (Int, Float64), bad in ("3 + 4*im", "3 + 4", "1+2ij", "1im-3im", "++4im")
@test_throws ArgumentError parse(Complex{T}, bad)
end
@test_throws ArgumentError parse(Complex{Int}, "3 + 4.2im")
end

0 comments on commit 627f0d3

Please sign in to comment.