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
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ Library improvements
* The function `randn` now accepts complex arguments (`Complex{T <: AbstractFloat}`)
([#21973]).

* `parse(Complex{T}, string)` is now implemented ([#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
Original file line number Diff line number Diff line change
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
58 changes: 52 additions & 6 deletions base/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ 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"`. If the string does not contain a valid number, an error is raised.

```jldoctest
julia> parse(Int, "1234")
Expand Down Expand Up @@ -215,7 +217,6 @@ 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))
Expand All @@ -228,11 +229,56 @@ tryparse(::Type{T}, s::AbstractString) where {T<:Union{Float32,Float64}} = trypa

tryparse(::Type{Float16}, s::AbstractString) = convert(Nullable{Float16}, tryparse(Float32, s))

function parse(::Type{T}, s::AbstractString) where T<:AbstractFloat

## string to complex functions ##

function tryparse(::Type{Complex{T}}, s::Union{String,SubString{String}}) where {T<:Real}
# skip initial whitespace
i = start(s)
e = endof(s)
while i ≤ e && isspace(s[i])
i = nextind(s, i)
end
i > e && return Nullable{Complex{T}}()

# 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
if i₊ == 0 # purely real value
return Nullable{Complex{T}}(tryparse(T, s))
end

# find trailing im/i/j
iᵢ = rsearch(s, ('m','i','j'), e)
iᵢ < i₊ && return Nullable{Complex{T}}()
if s[iᵢ] == 'm' # im
iᵢ -= 1
s[iᵢ] == 'i' || return Nullable{Complex{T}}()
end

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

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

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

# the ±1 indexing above for ascii chars is specific to String, so convert:
tryparse(T::Type{<:Complex}, s::AbstractString) = tryparse(T, String(s))

function parse(::Type{T}, s::AbstractString) where T<:Union{AbstractFloat,Complex}
result = tryparse(T, s)
if isnull(result)
throw(ArgumentError("cannot parse $(repr(s)) as $T"))
end
return unsafe_get(result)
end

20 changes: 4 additions & 16 deletions test/mpfr.jl
Original file line number Diff line number Diff line change
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
18 changes: 18 additions & 0 deletions test/parse.jl
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,21 @@ 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)
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{Int}, SubString("xxxxxx1+2imxxxx", 7, 10)) === 1+2im
end