From d4229beb7e8d2c665fe6bd3fc9624ec2ffa4d096 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 12 Jul 2017 03:28:46 -0500 Subject: [PATCH 1/2] Make DomainError more informative --- NEWS.md | 7 +++++-- base/boot.jl | 7 ++++++- base/combinatorics.jl | 4 ++-- base/dSFMT.jl | 2 +- base/dates/rounding.jl | 12 ++++++------ base/deprecated.jl | 6 ++++++ base/docs/helpdb/Base.jl | 7 ++++--- base/float.jl | 7 ++++--- base/floatfuncs.jl | 2 +- base/gmp.jl | 8 ++++---- base/intfuncs.jl | 23 +++++++++++++++-------- base/libuv.jl | 4 ++-- base/linalg/eigen.jl | 10 ++++++---- base/math.jl | 40 ++++++++++++++++++++++++++++++++-------- base/mpfr.jl | 25 ++++++++++++++----------- base/random.jl | 6 ++++-- base/rational.jl | 3 ++- base/replutil.jl | 33 +++++++++------------------------ base/special/gamma.jl | 2 +- base/special/log.jl | 8 ++++---- base/special/trig.jl | 12 ++++++------ base/strings/util.jl | 2 +- test/replutil.jl | 9 +++++++-- 23 files changed, 142 insertions(+), 97 deletions(-) diff --git a/NEWS.md b/NEWS.md index f96740958f968..846a19727f782 100644 --- a/NEWS.md +++ b/NEWS.md @@ -157,8 +157,11 @@ Deprecated or removed * `fieldnames` now operates only on types. To get the names of fields in an object, use `fieldnames(typeof(x))` ([#22350]). - * `InexactError` now takes arguments: `InexactError(func::Symbol, - type, -3)` now prints as `ERROR: InexactError: func(type, -3)`. ([#20005]) + * `InexactError` and `DomainError` now take + arguments. `InexactError(func::Symbol, type, -3)` now prints as + `ERROR: InexactError: func(type, -3)`, and `DomainError(val, + [msg])` prints as `ERROR: DomainError with val:\nmsg`. ([#20005], + [#22751]) Julia v0.6.0 Release Notes ========================== diff --git a/base/boot.jl b/base/boot.jl index 5ba20e39a6704..e5107db7faee8 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -205,7 +205,6 @@ struct BoundsError <: Exception BoundsError(a::ANY, i) = (@_noinline_meta; new(a,i)) end struct DivideError <: Exception end -struct DomainError <: Exception end struct OverflowError <: Exception end struct OutOfMemoryError <: Exception end struct ReadOnlyMemoryError<: Exception end @@ -216,6 +215,12 @@ struct UndefVarError <: Exception var::Symbol end struct InterruptException <: Exception end +struct DomainError <: Exception + val + msg + DomainError(val::ANY) = (@_noinline_meta; new(val)) + DomainError(val::ANY, msg::ANY) = (@_noinline_meta; new(val, msg)) +end mutable struct TypeError <: Exception func::Symbol context::AbstractString diff --git a/base/combinatorics.jl b/base/combinatorics.jl index 137fb439c6ec6..d0993cd0bc11e 100644 --- a/base/combinatorics.jl +++ b/base/combinatorics.jl @@ -15,7 +15,7 @@ for n in 2:34 end function factorial_lookup(n::Integer, table, lim) - n < 0 && throw(DomainError()) + n < 0 && throw(DomainError(n, "`n` must not be negative.")) n > lim && throw(OverflowError()) n == 0 && return one(n) @inbounds f = table[n] @@ -34,7 +34,7 @@ else end function gamma(n::Union{Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64}) - n < 0 && throw(DomainError()) + n < 0 && throw(DomainError(n, "`n` must not be negative.")) n == 0 && return Inf n <= 2 && return 1.0 n > 20 && return gamma(Float64(n)) diff --git a/base/dSFMT.jl b/base/dSFMT.jl index bd22f22a2fa39..8bc18f3bf68d7 100644 --- a/base/dSFMT.jl +++ b/base/dSFMT.jl @@ -25,7 +25,7 @@ mutable struct DSFMT_state val::Vector{Int32} DSFMT_state(val::Vector{Int32} = zeros(Int32, JN32)) = - new(length(val) == JN32 ? val : throw(DomainError())) + new(length(val) == JN32 ? val : throw(DomainError(length(val), string("Expected length ", JN32, '.')))) end copy!(dst::DSFMT_state, src::DSFMT_state) = (copy!(dst.val, src.val); dst) diff --git a/base/dates/rounding.jl b/base/dates/rounding.jl index 8adbf80902b57..6a4a40d38ae13 100644 --- a/base/dates/rounding.jl +++ b/base/dates/rounding.jl @@ -40,13 +40,13 @@ Takes the given `DateTime` and returns the number of milliseconds since the roun datetime2epochms(dt::DateTime) = value(dt) - DATETIMEEPOCH function Base.floor(dt::Date, p::Year) - value(p) < 1 && throw(DomainError()) + value(p) < 1 && throw(DomainError(p)) years = year(dt) return Date(years - mod(years, value(p))) end function Base.floor(dt::Date, p::Month) - value(p) < 1 && throw(DomainError()) + value(p) < 1 && throw(DomainError(p)) y, m = yearmonth(dt) months_since_epoch = y * 12 + m - 1 month_offset = months_since_epoch - mod(months_since_epoch, value(p)) @@ -56,14 +56,14 @@ function Base.floor(dt::Date, p::Month) end function Base.floor(dt::Date, p::Week) - value(p) < 1 && throw(DomainError()) + value(p) < 1 && throw(DomainError(p)) days = value(dt) - WEEKEPOCH days = days - mod(days, value(Day(p))) return Date(UTD(WEEKEPOCH + Int64(days))) end function Base.floor(dt::Date, p::Day) - value(p) < 1 && throw(DomainError()) + value(p) < 1 && throw(DomainError(p)) days = date2epochdays(dt) return epochdays2date(days - mod(days, value(p))) end @@ -71,7 +71,7 @@ end Base.floor(dt::DateTime, p::DatePeriod) = DateTime(Base.floor(Date(dt), p)) function Base.floor(dt::DateTime, p::TimePeriod) - value(p) < 1 && throw(DomainError()) + value(p) < 1 && throw(DomainError(p)) milliseconds = datetime2epochms(dt) return epochms2datetime(milliseconds - mod(milliseconds, value(Millisecond(p)))) end @@ -166,7 +166,7 @@ Base.round(dt::TimeType, p::Period, r::RoundingMode{:Up}) = Base.ceil(dt, p) # No implementation of other `RoundingMode`s: rounding to nearest "even" is skipped because # "even" is not defined for Period; rounding toward/away from zero is skipped because ISO # 8601's year 0000 is not really "zero". -Base.round(::TimeType, ::Period, ::RoundingMode) = throw(DomainError()) +Base.round(::TimeType, p::Period, ::RoundingMode) = throw(DomainError(p)) # Default to RoundNearestTiesUp. Base.round(dt::TimeType, p::Period) = Base.round(dt, p, RoundNearestTiesUp) diff --git a/base/deprecated.jl b/base/deprecated.jl index a619da84ad349..0b71e3f0c1313 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1561,6 +1561,12 @@ function InexactError() InexactError(:none, Any, nothing) end +# PR #22751 +function DomainError() + depwarn("DomainError now supports arguments, use `DomainError(value)` or `DomainError(value, msg)` instead.", :DomainError) + DomainError(nothing) +end + # PR #22703 @deprecate Bidiagonal(dv::AbstractVector, ev::AbstractVector, isupper::Bool) Bidiagonal(dv, ev, ifelse(isupper, :U, :L)) @deprecate Bidiagonal(dv::AbstractVector, ev::AbstractVector, uplo::Char) Bidiagonal(dv, ev, ifelse(uplo == 'U', :U, :L)) diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index a24f9a7583985..f534effd26f9b 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -1402,14 +1402,15 @@ The highest value representable by the given (real) numeric `DataType`. typemax """ - DomainError() + DomainError(val) + DomainError(val, msg) -The arguments to a function or constructor are outside the valid domain. +The argument `val` to a function or constructor is outside the valid domain. # Examples ```jldoctest julia> sqrt(-1) -ERROR: DomainError: +ERROR: DomainError with -1: sqrt will only return a complex result if called with a complex argument. Try sqrt(complex(x)). Stacktrace: [1] sqrt(::Int64) at ./math.jl:443 diff --git a/base/float.jl b/base/float.jl index 0b7306d64fc16..40242fab6b112 100644 --- a/base/float.jl +++ b/base/float.jl @@ -443,17 +443,18 @@ for op in (:<, :<=, :isless) end function cmp(x::AbstractFloat, y::AbstractFloat) - (isnan(x) || isnan(y)) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) + isnan(y) && throw(DomainError(y, "`y` cannot be NaN.")) ifelse(xy, 1, 0)) end function cmp(x::Real, y::AbstractFloat) - isnan(y) && throw(DomainError()) + isnan(y) && throw(DomainError(y, "`y` cannot be NaN.")) ifelse(xy, 1, 0)) end function cmp(x::AbstractFloat, y::Real) - isnan(x) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) ifelse(xy, 1, 0)) end diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index a5973c7d99986..340329c545806 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -132,7 +132,7 @@ function _signif_og(x, digits, base) end function signif(x::Real, digits::Integer, base::Integer=10) - digits < 1 && throw(DomainError()) + digits < 1 && throw(DomainError(digits, "`digits` cannot be less than 1.")) x = float(x) (x == 0 || !isfinite(x)) && return x diff --git a/base/gmp.jl b/base/gmp.jl index a15cc43d03fa4..566e8b70ec354 100644 --- a/base/gmp.jl +++ b/base/gmp.jl @@ -403,7 +403,7 @@ function invmod(x::BigInt, y::BigInt) return z end if (y==0 || MPZ.invert!(z, x, ya) == 0) - throw(DomainError()) + throw(DomainError(y)) end # GMP always returns a positive inverse; we instead want to # normalize such that div(z, y) == 0, i.e. we want a negative z @@ -474,7 +474,7 @@ cmp(x::BigInt, y::CulongMax) = MPZ.cmp_ui(x, y) cmp(x::BigInt, y::Integer) = cmp(x, big(y)) cmp(x::Integer, y::BigInt) = -cmp(y, x) -cmp(x::BigInt, y::CdoubleMax) = isnan(y) ? throw(DomainError()) : MPZ.cmp_d(x, y) +cmp(x::BigInt, y::CdoubleMax) = isnan(y) ? throw(DomainError(y, "`y` cannot be NaN.")) : MPZ.cmp_d(x, y) cmp(x::CdoubleMax, y::BigInt) = -cmp(y, x) isqrt(x::BigInt) = MPZ.sqrt(x) @@ -482,7 +482,7 @@ isqrt(x::BigInt) = MPZ.sqrt(x) ^(x::BigInt, y::Culong) = MPZ.pow_ui(x, y) function bigint_pow(x::BigInt, y::Integer) - if y<0; throw(DomainError()); end + if y<0; throw(DomainError(y, "`y` cannot be negative.")); end if x== 1; return x; end if x==-1; return isodd(y) ? x : -x; end if y>typemax(Culong) @@ -607,7 +607,7 @@ function base(b::Integer, n::BigInt, pad::Integer) end function ndigits0zpb(x::BigInt, b::Integer) - b < 2 && throw(DomainError()) + b < 2 && throw(DomainError(b, "`b` cannot be less than 2.")) x.size == 0 && return 0 # for consistency with other ndigits0z methods if ispow2(b) && 2 <= b <= 62 # GMP assumes b is in this range MPZ.sizeinbase(x, b) diff --git a/base/intfuncs.jl b/base/intfuncs.jl index 0d0c2af9d5ed0..23db5c5bd8f94 100644 --- a/base/intfuncs.jl +++ b/base/intfuncs.jl @@ -147,7 +147,8 @@ julia> invmod(5,6) """ function invmod(n::T, m::T) where T<:Integer g, x, y = gcdx(n, m) - (g != 1 || m == 0) && throw(DomainError()) + g != 1 && throw(DomainError((n, m), "Greatest common divisor is $g.")) + m == 0 && throw(DomainError(m, "`m` must not be 0.")) # Note that m might be negative here. # For unsigned T, x might be close to typemax; add m to force a wrap-around. r = mod(x + m, m) @@ -159,6 +160,10 @@ invmod(n::Integer, m::Integer) = invmod(promote(n,m)...) # ^ for any x supporting * to_power_type(x::Number) = oftype(x*x, x) to_power_type(x) = x +@noinline throw_domerr_powbysq(p) = throw(DomainError(p, + string("Cannot raise an integer x to a negative power ", p, '.', + "\nMake x a float by adding a zero decimal (e.g., 2.0^$p instead ", + "of 2^$p), or write 1/x^$(-p), float(x)^$p, or (x//1)^$p"))) function power_by_squaring(x_, p::Integer) x = to_power_type(x_) if p == 1 @@ -170,7 +175,7 @@ function power_by_squaring(x_, p::Integer) elseif p < 0 x == 1 && return copy(x) x == -1 && return iseven(p) ? one(x) : copy(x) - throw(DomainError()) + throw_domerr_powbysq(p) end t = trailing_zeros(p) + 1 p >>= t @@ -190,7 +195,7 @@ function power_by_squaring(x_, p::Integer) end power_by_squaring(x::Bool, p::Unsigned) = ((p==0) | x) function power_by_squaring(x::Bool, p::Integer) - p < 0 && !x && throw(DomainError()) + p < 0 && !x && throw_domerr_powbysq(p) return (p==0) | x end @@ -348,7 +353,8 @@ julia> nextpow(4, 16) See also [`prevpow`](@ref). """ function nextpow(a::Real, x::Real) - (a <= 1 || x <= 0) && throw(DomainError()) + a <= 1 && throw(DomainError(a, "`a` must be greater than 1.")) + x <= 0 && throw(DomainError(x, "`x` must be positive.")) x <= 1 && return one(a) n = ceil(Integer,log(a, x)) p = a^(n-1) @@ -379,7 +385,8 @@ julia> prevpow(4, 16) See also [`nextpow`](@ref). """ function prevpow(a::Real, x::Real) - (a <= 1 || x < 1) && throw(DomainError()) + a <= 1 && throw(DomainError(a, "`a` must be greater than 1.")) + x < 1 && throw(DomainError(x, "`x` must be ≥ 1.")) n = floor(Integer,log(a, x)) p = a^(n+1) p <= x ? p : a^n @@ -498,7 +505,7 @@ function ndigits0z(x::Integer, b::Integer) elseif b > 1 ndigits0zpb(x, b) else - throw(DomainError()) + throw(DomainError(b, "The base `b` must not be in `[-1, 0, 1]`.")) end end @@ -582,7 +589,7 @@ const base62digits = ['0':'9';'A':'Z';'a':'z'] function base(b::Int, x::Integer, pad::Int, neg::Bool) - (x >= 0) | (b < 0) || throw(DomainError()) + (x >= 0) | (b < 0) || throw(DomainError(x, "For negative `x`, `b` must be negative.")) 2 <= abs(b) <= 62 || throw(ArgumentError("base must satisfy 2 ≤ abs(base) ≤ 62, got $b")) digits = abs(b) <= 36 ? base36digits : base62digits i = neg + ndigits(x, b, pad) @@ -800,7 +807,7 @@ function isqrt(x::Union{Int64,UInt64,Int128,UInt128}) end function factorial(n::Integer) - n < 0 && throw(DomainError()) + n < 0 && throw(DomainError(n, "`n` must be nonnegative.")) local f::typeof(n*n), i::typeof(n*n) f = 1 for i = 2:n diff --git a/base/libuv.jl b/base/libuv.jl index 99bc52281c96f..0a934484642f2 100644 --- a/base/libuv.jl +++ b/base/libuv.jl @@ -7,14 +7,14 @@ include(string(length(Core.ARGS) >= 2 ? Core.ARGS[2] : "", "uv_constants.jl")) # convert UV handle data to julia object, checking for null function uv_sizeof_handle(handle) if !(UV_UNKNOWN_HANDLE < handle < UV_HANDLE_TYPE_MAX) - throw(DomainError()) + throw(DomainError(handle)) end ccall(:uv_handle_size,Csize_t,(Int32,),handle) end function uv_sizeof_req(req) if !(UV_UNKNOWN_REQ < req < UV_REQ_TYPE_MAX) - throw(DomainError()) + throw(DomainError(req)) end ccall(:uv_req_size,Csize_t,(Int32,),req) end diff --git a/base/linalg/eigen.jl b/base/linalg/eigen.jl index 98e4b940fc95a..d0731266a5018 100644 --- a/base/linalg/eigen.jl +++ b/base/linalg/eigen.jl @@ -226,7 +226,8 @@ julia> A = [0 im; -1 0] -1+0im 0+0im julia> eigmax(A) -ERROR: DomainError: +ERROR: DomainError with Complex{Int64}[0+0im 0+1im; -1+0im 0+0im]: +`A` cannot have complex eigenvalues. Stacktrace: [1] #eigmax#52(::Bool, ::Bool, ::Function, ::Array{Complex{Int64},2}) at ./linalg/eigen.jl:238 [2] eigmax(::Array{Complex{Int64},2}) at ./linalg/eigen.jl:236 @@ -235,7 +236,7 @@ Stacktrace: function eigmax(A::Union{Number, StridedMatrix}; permute::Bool=true, scale::Bool=true) v = eigvals(A, permute = permute, scale = scale) if eltype(v)<:Complex - throw(DomainError()) + throw(DomainError(A, "`A` cannot have complex eigenvalues.")) end maximum(v) end @@ -268,7 +269,8 @@ julia> A = [0 im; -1 0] -1+0im 0+0im julia> eigmin(A) -ERROR: DomainError: +ERROR: DomainError with Complex{Int64}[0+0im 0+1im; -1+0im 0+0im]: +`A` cannot have complex eigenvalues. Stacktrace: [1] #eigmin#53(::Bool, ::Bool, ::Function, ::Array{Complex{Int64},2}) at ./linalg/eigen.jl:280 [2] eigmin(::Array{Complex{Int64},2}) at ./linalg/eigen.jl:278 @@ -277,7 +279,7 @@ Stacktrace: function eigmin(A::Union{Number, StridedMatrix}; permute::Bool=true, scale::Bool=true) v = eigvals(A, permute = permute, scale = scale) if eltype(v)<:Complex - throw(DomainError()) + throw(DomainError(A, "`A` cannot have complex eigenvalues.")) end minimum(v) end diff --git a/base/math.jl b/base/math.jl index 485d24884f180..3bbbe0e4dc909 100644 --- a/base/math.jl +++ b/base/math.jl @@ -27,6 +27,16 @@ using Core.Intrinsics: sqrt_llvm const IEEEFloat = Union{Float16, Float32, Float64} +@noinline function throw_complex_domainerror(f, x) + throw(DomainError(x, string("$f will only return a complex result if called with a ", + "complex argument. Try $f(Complex(x))."))) +end +@noinline function throw_exp_domainerror(x) + throw(DomainError(x, string("Exponentiation yielding a complex result requires a ", + "complex argument.\nReplace x^y with (x+0im)^y, ", + "Complex(x)^y, or similar."))) +end + for T in (Float16, Float32, Float64) @eval significand_bits(::Type{$T}) = $(trailing_ones(significand_mask(T))) @eval exponent_bits(::Type{$T}) = $(sizeof(T)*8 - significand_bits(T) - 1) @@ -293,7 +303,7 @@ end # utility for converting NaN return to DomainError # the branch in nan_dom_err prevents its callers from inlining, so be sure to force it # until the heuristics can be improved -@inline nan_dom_err(f, x) = isnan(f) & !isnan(x) ? throw(DomainError()) : f +@inline nan_dom_err(out, x) = isnan(out) & !isnan(x) ? throw(DomainError(x, "NaN result for non-NaN input.")) : out # functions that return NaN on non-NaN argument for domain error """ @@ -426,13 +436,13 @@ Compute sine and cosine of `x`, where `x` is in radians. @inline function sincos(x) res = Base.FastMath.sincos_fast(x) if (isnan(res[1]) | isnan(res[2])) & !isnan(x) - throw(DomainError()) + throw(DomainError(x, "NaN result for non-NaN input.")) end return res end @inline function sqrt(x::Union{Float32,Float64}) - x < zero(x) && throw(DomainError()) + x < zero(x) && throw_complex_domainerror(:sqrt, x) sqrt_llvm(x) end @@ -457,7 +467,7 @@ julia> hypot(a, a) 1.4142135623730951e10 julia> √(a^2 + a^2) # a^2 overflows -ERROR: DomainError: +ERROR: DomainError with -2914184810805067776: sqrt will only return a complex result if called with a complex argument. Try sqrt(complex(x)). Stacktrace: [1] sqrt(::Int64) at ./math.jl:447 @@ -581,11 +591,13 @@ ldexp(x::Float16, q::Integer) = Float16(ldexp(Float32(x), q)) Get the exponent of a normalized floating-point number. """ function exponent(x::T) where T<:IEEEFloat + @noinline throw1(x) = throw(DomainError(x, "Cannot be NaN or Inf.")) + @noinline throw2(x) = throw(DomainError(x, "Cannot be subnormal converted to 0.")) xs = reinterpret(Unsigned, x) & ~sign_mask(T) - xs >= exponent_mask(T) && return throw(DomainError()) # NaN or Inf + xs >= exponent_mask(T) && throw1(x) k = Int(xs >> significand_bits(T)) if k == 0 # x is subnormal - xs == 0 && throw(DomainError()) + xs == 0 && throw2(x) m = leading_zeros(xs) - exponent_bits(T) k = 1 - m end @@ -707,8 +719,20 @@ function modf(x::Float64) f, _modf_temp[] end -@inline ^(x::Float64, y::Float64) = nan_dom_err(ccall("llvm.pow.f64", llvmcall, Float64, (Float64, Float64), x, y), x + y) -@inline ^(x::Float32, y::Float32) = nan_dom_err(ccall("llvm.pow.f32", llvmcall, Float32, (Float32, Float32), x, y), x + y) +@inline function ^(x::Float64, y::Float64) + z = ccall("llvm.pow.f64", llvmcall, Float64, (Float64, Float64), x, y) + if isnan(z) & !isnan(x+y) + throw_exp_domainerror(x) + end + z +end +@inline function ^(x::Float32, y::Float32) + z = ccall("llvm.pow.f32", llvmcall, Float32, (Float32, Float32), x, y) + if isnan(z) & !isnan(x+y) + throw_exp_domainerror(x) + end + z +end @inline ^(x::Float64, y::Integer) = x ^ Float64(y) @inline ^(x::Float32, y::Integer) = x ^ Float32(y) @inline ^(x::Float16, y::Integer) = Float16(Float32(x) ^ Float32(y)) diff --git a/base/mpfr.jl b/base/mpfr.jl index 0da255045918b..8adeafd776ae5 100644 --- a/base/mpfr.jl +++ b/base/mpfr.jl @@ -485,7 +485,7 @@ function sqrt(x::BigFloat) z = BigFloat() ccall((:mpfr_sqrt, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[]) if isnan(z) - throw(DomainError()) + throw(DomainError(x, "NaN result for non-NaN input.")) end return z end @@ -560,7 +560,7 @@ ldexp(x::BigFloat, n::Integer) = x*exp2(BigFloat(n)) function factorial(x::BigFloat) if x < 0 || !isinteger(x) - throw(DomainError()) + throw(DomainError(x, "Must be a non-negative integer.")) end ui = convert(Culong, x) z = BigFloat() @@ -577,7 +577,8 @@ end for f in (:log, :log2, :log10) @eval function $f(x::BigFloat) if x < 0 - throw(DomainError()) + throw(DomainError(x, string($f, " will only return a complex result if called ", + "with a complex argument. Try ", $f, "(complex(x))."))) end z = BigFloat() ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[]) @@ -587,7 +588,8 @@ end function log1p(x::BigFloat) if x < -1 - throw(DomainError()) + throw(DomainError(x, string("log1p will only return a complex result if called ", + "with a complex argument. Try log1p(complex(x))."))) end z = BigFloat() ccall((:mpfr_log1p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[]) @@ -656,7 +658,7 @@ for f in (:sin,:cos,:tan,:sec,:csc, z = BigFloat() ccall(($(string(:mpfr_,f)), :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}, Int32), &z, &x, ROUNDING_MODE[]) if isnan(z) - throw(DomainError()) + throw(DomainError(x, "NaN result for non-NaN input.")) end return z end @@ -687,22 +689,23 @@ end >(x::BigFloat, y::BigFloat) = ccall((:mpfr_greater_p, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigFloat}), &x, &y) != 0 function cmp(x::BigFloat, y::BigInt) - isnan(x) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) ccall((:mpfr_cmp_z, :libmpfr), Int32, (Ptr{BigFloat}, Ptr{BigInt}), &x, &y) end function cmp(x::BigFloat, y::ClongMax) - isnan(x) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) ccall((:mpfr_cmp_si, :libmpfr), Int32, (Ptr{BigFloat}, Clong), &x, y) end function cmp(x::BigFloat, y::CulongMax) - isnan(x) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) ccall((:mpfr_cmp_ui, :libmpfr), Int32, (Ptr{BigFloat}, Culong), &x, y) end cmp(x::BigFloat, y::Integer) = cmp(x,big(y)) cmp(x::Integer, y::BigFloat) = -cmp(y,x) function cmp(x::BigFloat, y::CdoubleMax) - (isnan(x) || isnan(y)) && throw(DomainError()) + isnan(x) && throw(DomainError(x, "`x` cannot be NaN.")) + isnan(y) && throw(DomainError(y, "`y` cannot be NaN.")) ccall((:mpfr_cmp_d, :libmpfr), Int32, (Ptr{BigFloat}, Cdouble), &x, y) end cmp(x::CdoubleMax, y::BigFloat) = -cmp(y,x) @@ -742,7 +745,7 @@ Set the precision (in bits) to be used for `T` arithmetic. """ function setprecision(::Type{BigFloat}, precision::Int) if precision < 2 - throw(DomainError()) + throw(DomainError(precision, "`precision` cannot be less than 2.")) end DEFAULT_PRECISION[end] = precision end @@ -789,7 +792,7 @@ end function exponent(x::BigFloat) if x == 0 || !isfinite(x) - throw(DomainError()) + throw(DomainError(x, "`x` must be non-zero and finite.")) end # The '- 1' is to make it work as Base.exponent return ccall((:mpfr_get_exp, :libmpfr), Clong, (Ptr{BigFloat},), &x) - 1 diff --git a/base/random.jl b/base/random.jl index 3f23af00d977f..b41ee77c0735c 100644 --- a/base/random.jl +++ b/base/random.jl @@ -82,7 +82,9 @@ mutable struct MersenneTwister <: AbstractRNG idx::Int function MersenneTwister(seed, state, vals, idx) - length(vals) == MTCacheLength && 0 <= idx <= MTCacheLength || throw(DomainError()) + if !(length(vals) == MTCacheLength && 0 <= idx <= MTCacheLength) + throw(DomainError(idx, "`length(vals)` and `idx` must be consistent with $MTCacheLength")) + end new(seed, state, vals, idx) end end @@ -224,7 +226,7 @@ function make_seed() end function make_seed(n::Integer) - n < 0 && throw(DomainError()) + n < 0 && throw(DomainError(n, "`n` must be non-negative.")) seed = UInt32[] while true push!(seed, n & 0xffffffff) diff --git a/base/rational.jl b/base/rational.jl index b6af61273dfce..303e07f6bf55f 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -290,7 +290,8 @@ for rel in (:<,:<=,:cmp) for (Tx,Ty) in ((Rational,AbstractFloat), (AbstractFloat,Rational)) @eval function ($rel)(x::$Tx, y::$Ty) if isnan(x) || isnan(y) - $(rel == :cmp ? :(throw(DomainError())) : :(return false)) + $(rel == :cmp ? :(throw(DomainError((x,y), "Inputs cannot be NaN."))) : + :(return false)) end xn, xp, xd = decompose(x) diff --git a/base/replutil.jl b/base/replutil.jl index 5a07e8a45d1a4..b5dd17dc1d2fa 100644 --- a/base/replutil.jl +++ b/base/replutil.jl @@ -232,31 +232,16 @@ end showerror(io::IO, ex::InitError) = showerror(io, ex, []) function showerror(io::IO, ex::DomainError, bt; backtrace=true) - print(io, "DomainError:") - for b in bt - for code in StackTraces.lookup(b) - if code.from_c - continue - elseif code.func === :nan_dom_err - continue - elseif code.func in (:log, :log2, :log10, :sqrt) - print(io, "\n$(code.func) will only return a complex result if called ", - "with a complex argument. Try $(string(code.func))(complex(x)).") - elseif (code.func === :^ && - (code.file === Symbol("intfuncs.jl") || code.file === Symbol(joinpath(".", "intfuncs.jl")))) || - code.func === :power_by_squaring #3024 - print(io, "\nCannot raise an integer x to a negative power -n. ", - "\nMake x a float by adding a zero decimal (e.g. 2.0^-n instead ", - "of 2^-n), or write 1/x^n, float(x)^-n, or (x//1)^-n.") - elseif code.func === :^ && - (code.file === Symbol("math.jl") || code.file === Symbol(joinpath(".", "math.jl"))) - print(io, "\nExponentiation yielding a complex result requires a complex ", - "argument.\nReplace x^y with (x+0im)^y, Complex(x)^y, or similar.") - end - @goto showbacktrace - end + if isa(ex.val, AbstractArray) + compact = get(io, :compact, true) + limit = get(io, :limit, true) + print(IOContext(io, compact=compact, limit=limit), "DomainError with ", ex.val) + else + print(io, "DomainError with ", ex.val) + end + if isdefined(ex, :msg) + print(io, ":\n", ex.msg) end - @label showbacktrace backtrace && show_backtrace(io, bt) nothing end diff --git a/base/special/gamma.jl b/base/special/gamma.jl index 7415200b409ce..b6b4539fb46fc 100644 --- a/base/special/gamma.jl +++ b/base/special/gamma.jl @@ -31,7 +31,7 @@ Compute the logarithmic factorial of a nonnegative integer `x`. Equivalent to [`lgamma`](@ref) of `x + 1`, but `lgamma` extends this function to non-integer `x`. """ -lfact(x::Integer) = x < 0 ? throw(DomainError()) : lgamma(x + oneunit(x)) +lfact(x::Integer) = x < 0 ? throw(DomainError(x, "`x` must be non-negative.")) : lgamma(x + oneunit(x)) """ lgamma(x) diff --git a/base/special/log.jl b/base/special/log.jl index d317101794155..e62ddf541705e 100644 --- a/base/special/log.jl +++ b/base/special/log.jl @@ -282,7 +282,7 @@ function log(x::Float64) elseif isnan(x) NaN else - throw(DomainError()) + throw_complex_domainerror(x, :log) end end @@ -318,7 +318,7 @@ function log(x::Float32) elseif isnan(x) NaN32 else - throw(DomainError()) + throw_complex_domainerror(x, :log) end end @@ -353,7 +353,7 @@ function log1p(x::Float64) elseif isnan(x) NaN else - throw(DomainError()) + throw_complex_domainerror(x, :log1p) end end @@ -386,7 +386,7 @@ function log1p(x::Float32) elseif isnan(x) NaN32 else - throw(DomainError()) + throw_complex_domainerror(x, :log1p) end end diff --git a/base/special/trig.jl b/base/special/trig.jl index e563edce0955f..6c1c81c300d93 100644 --- a/base/special/trig.jl +++ b/base/special/trig.jl @@ -106,7 +106,7 @@ Compute ``\\sin(\\pi x)`` more accurately than `sin(pi*x)`, especially for large function sinpi(x::T) where T<:AbstractFloat if !isfinite(x) isnan(x) && return x - throw(DomainError()) + throw(DomainError(x, "`x` cannot be infinite.")) end ax = abs(x) @@ -136,7 +136,7 @@ end function sinpi(x::T) where T<:Union{Integer,Rational} Tf = float(T) if !isfinite(x) - throw(DomainError()) + throw(DomainError(x, "`x` must be finite.")) end # until we get an IEEE remainder function (#9283) @@ -169,7 +169,7 @@ Compute ``\\cos(\\pi x)`` more accurately than `cos(pi*x)`, especially for large function cospi(x::T) where T<:AbstractFloat if !isfinite(x) isnan(x) && return x - throw(DomainError()) + throw(DomainError(x, "`x` cannot be infinite.")) end ax = abs(x) @@ -194,7 +194,7 @@ end # Integers and Rationals function cospi(x::T) where T<:Union{Integer,Rational} if !isfinite(x) - throw(DomainError()) + throw(DomainError(x, "`x` must be finite.")) end ax = abs(x) @@ -371,7 +371,7 @@ deg2rad_ext(x::Real) = deg2rad(x) # Fallback function sind(x::Real) if isinf(x) - return throw(DomainError()) + return throw(DomainError(x, "`x` cannot be infinite.")) elseif isnan(x) return oftype(x,NaN) end @@ -402,7 +402,7 @@ end function cosd(x::Real) if isinf(x) - return throw(DomainError()) + return throw(DomainError(x, "`x` cannot be infinite.")) elseif isnan(x) return oftype(x,NaN) end diff --git a/base/strings/util.jl b/base/strings/util.jl index f7707c3fc8866..ccee670bdc8a3 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -362,7 +362,7 @@ _replace(io, repl::Function, str, r, pattern) = # TODO: rename to `replace` when `replace` is removed from deprecated.jl function replace_new(str::String, pattern, repl, count::Integer) count == 0 && return str - count < 0 && throw(DomainError()) + count < 0 && throw(DomainError(count, "`count` must be non-negative.")) n = 1 e = endof(str) i = a = start(str) diff --git a/test/replutil.jl b/test/replutil.jl index d8c63b3b276bc..9f4d30aa120a3 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -254,11 +254,16 @@ end struct TypeWithIntParam{T <: Integer} end let undefvar err_str = @except_strbt sqrt(-1) DomainError - @test contains(err_str, "Try sqrt(complex(x)).") + @test contains(err_str, "Try sqrt(Complex(x)).") err_str = @except_strbt 2^(-1) DomainError - @test contains(err_str, "Cannot raise an integer x to a negative power -n") + @test contains(err_str, "Cannot raise an integer x to a negative power -1") err_str = @except_strbt (-1)^0.25 DomainError @test contains(err_str, "Exponentiation yielding a complex result requires a complex argument") + A = zeros(10, 10) + A[2,1] = 1 + A[1,2] = -1 + err_str = @except_strbt eigmax(A) DomainError + @test contains(err_str, "DomainError with [0.0 -1.0 …") err_str = @except_str (1, 2, 3)[4] BoundsError @test err_str == "BoundsError: attempt to access (1, 2, 3)\n at index [4]" From c437fc462a7cc2e1ca213655a9aeaeeea8bafece Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 12 Jul 2017 03:29:43 -0500 Subject: [PATCH 2/2] Better failure diagnostics for error paths in replutil tests --- test/replutil.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/replutil.jl b/test/replutil.jl index 9f4d30aa120a3..39451b44c05c3 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -182,13 +182,14 @@ macro except_str(expr, err_type) end macro except_strbt(expr, err_type) + errmsg = "expected failure, but no exception thrown for $expr" return quote let err = nothing try $(esc(expr)) catch err end - err === nothing && error("expected failure, but no exception thrown") + err === nothing && error($errmsg) @test typeof(err) === $(esc(err_type)) buf = IOBuffer() showerror(buf, err, catch_backtrace())