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

add parse(Complex{T}, s) #24713

Merged
merged 13 commits into from Dec 6, 2017
2 changes: 2 additions & 0 deletions NEWS.md
Expand Up @@ -325,6 +325,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
Copy link
Member

Choose a reason for hiding this comment

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

It might also be good to add some tests for parsing bogus stuff, like 1+2ij or 1im-3im.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.

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