diff --git a/NEWS.md b/NEWS.md index d3a5986905af1..a7004d6aba73b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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]). diff --git a/base/mpfr.jl b/base/mpfr.jl index 3048657ee59d3..8bff353fdca7f 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -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}() diff --git a/base/parse.jl b/base/parse.jl index d4fbff975321d..87447ba0a0a90 100644 --- a/base/parse.jl +++ b/base/parse.jl @@ -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") @@ -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) @@ -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")) @@ -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)) diff --git a/stdlib/DelimitedFiles/src/DelimitedFiles.jl b/stdlib/DelimitedFiles/src/DelimitedFiles.jl index 9326ac78938db..964bec6e98dac 100644 --- a/stdlib/DelimitedFiles/src/DelimitedFiles.jl +++ b/stdlib/DelimitedFiles/src/DelimitedFiles.jl @@ -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) @@ -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 diff --git a/stdlib/DelimitedFiles/test/runtests.jl b/stdlib/DelimitedFiles/test/runtests.jl index f41bd7d0728fa..092f6aa87f815 100644 --- a/stdlib/DelimitedFiles/test/runtests.jl +++ b/stdlib/DelimitedFiles/test/runtests.jl @@ -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 diff --git a/test/mpfr.jl b/test/mpfr.jl index 5dc38118ae3f9..2c73f7e6ea7d2 100644 --- a/test/mpfr.jl +++ b/test/mpfr.jl @@ -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 diff --git a/test/parse.jl b/test/parse.jl index e338020bf0f72..2792058e18237 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -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