diff --git a/NEWS.md b/NEWS.md index 5e7d433885bf32..5eee372dbfcbae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -300,6 +300,8 @@ Library improvements * The `MathConst` type has been renamed `Irrational` ([#11922]). + * `isapprox` now has simpler and more sensible default tolerances ([#12393]). + * Random numbers * Streamlined random number generation APIs [#8246]. @@ -1542,3 +1544,4 @@ Too numerous to mention. [#12034]: https://github.com/JuliaLang/julia/issues/12034 [#12087]: https://github.com/JuliaLang/julia/issues/12087 [#12137]: https://github.com/JuliaLang/julia/issues/12137 +[#12393]: https://github.com/JuliaLang/julia/issues/12393 diff --git a/base/floatfuncs.jl b/base/floatfuncs.jl index 6b307ae6783357..5d14382c3df6e2 100644 --- a/base/floatfuncs.jl +++ b/base/floatfuncs.jl @@ -165,33 +165,12 @@ for f in (:round, :ceil, :floor, :trunc) end end -# isapprox: Tolerant comparison of floating point numbers -function isapprox(x::FloatingPoint, y::FloatingPoint; rtol::Real=rtoldefault(x,y), atol::Real=atoldefault(x,y)) - (isinf(x) || isinf(y)) ? x == y : abs(x-y) <= atol + rtol.*max(abs(x), abs(y)) -end - -# promotion of non-floats -isapprox(x::Real, y::FloatingPoint; rtol::Real=rtoldefault(x, y), atol::Real=atoldefault(x, y)) = isapprox(promote(x, y)...; rtol=rtol, atol=atol) -isapprox(x::FloatingPoint, y::Real; rtol::Real=rtoldefault(x, y), atol::Real=atoldefault(x, y)) = isapprox(promote(x, y)...; rtol=rtol, atol=atol) - -# other real numbers -isapprox(x::Real, y::Real; rtol::Real=0, atol::Real=0) = abs(x-y) <= atol - -# complex numbers -isapprox(z::Complex, w::Complex; rtol::Real=rtoldefault(abs(z), abs(w)), atol::Real=atoldefault(abs(z), abs(w))) = abs(z-w) <= atol + rtol*max(abs(z), abs(w)) - -# real-complex combinations -isapprox(x::Real, z::Complex; rtol::Real=rtoldefault(x, abs(z)), atol::Real=atoldefault(x, abs(z))) = isapprox(complex(x), z; rtol=rtol, atol=atol) -isapprox(z::Complex, x::Real; rtol::Real=rtoldefault(x, abs(z)), atol::Real=atoldefault(x, abs(z))) = isapprox(complex(x), z; rtol=rtol, atol=atol) +# isapprox: approximate equality of numbers +isapprox(x::Number, y::Number; rtol::Real=rtoldefault(x,y), atol::Real=0) = + x == y || (isfinite(x) && isfinite(y) && abs(x-y) <= atol + rtol*max(abs(x), abs(y))) # default tolerance arguments -rtoldefault(x::FloatingPoint, y::FloatingPoint) = cbrt(max(eps(x), eps(y))) -atoldefault(x::FloatingPoint, y::FloatingPoint) = sqrt(max(eps(x), eps(y))) +rtoldefault{T<:FloatingPoint}(::Type{T}) = sqrt(eps(T)) +rtoldefault{T<:Real}(::Type{T}) = 0 +rtoldefault{T<:Number,S<:Number}(x::T, y::S) = rtoldefault(promote_type(real(T),real(S))) -# promotion of non-floats -for fun in (:rtoldefault, :atoldefault) - @eval begin - ($fun)(x::Real, y::FloatingPoint) = ($fun)(promote(x,y)...) - ($fun)(x::FloatingPoint, y::Real) = ($fun)(promote(x,y)...) - end -end diff --git a/doc/stdlib/math.rst b/doc/stdlib/math.rst index 01f4b7a8b0e6bb..e3353f5b8a25e9 100644 --- a/doc/stdlib/math.rst +++ b/doc/stdlib/math.rst @@ -417,17 +417,11 @@ Mathematical Operators Mathematical Functions ---------------------- -.. function:: isapprox(x::Number, y::Number; rtol::Real=cbrt(maxeps), atol::Real=sqrt(maxeps)) +.. function:: isapprox(x::Number, y::Number; rtol::Real=sqrt(eps), atol::Real=0) - Inexact equality comparison - behaves slightly different depending on types of input args: + Inexact equality comparison: ``true`` if ``abs(x-y) <= atol + rtol*max(abs(x), abs(y))``. The default ``atol`` is zero and the default ``rtol`` depends on the types of ``x`` and ``y``. - * For ``FloatingPoint`` numbers, ``isapprox`` returns ``true`` if ``abs(x-y) <= atol + rtol*max(abs(x), abs(y))``. - - * For ``Integer`` and ``Rational`` numbers, ``isapprox`` returns ``true`` if ``abs(x-y) <= atol``. The `rtol` argument is ignored. If one of ``x`` and ``y`` is ``FloatingPoint``, the other is promoted, and the method above is called instead. - - * For ``Complex`` numbers, the distance in the complex plane is compared, using the same criterion as above. - - For default tolerance arguments, ``maxeps = max(eps(abs(x)), eps(abs(y)))``. + For real or complex floating-point values, ``rtol`` defaults to ``sqrt(eps(typeof(real(x-y))))``. This corresponds to requiring equality of about half of the significand digits. For other types, ``rtol`` defaults to zero. .. function:: sin(x) diff --git a/test/fastmath.jl b/test/fastmath.jl index 3cc569106467d7..5f5346db7e0114 100644 --- a/test/fastmath.jl +++ b/test/fastmath.jl @@ -138,18 +138,22 @@ for T in (Complex64, Complex128, Complex{BigFloat}) half = (1+1im)/T(2) third = (1-1im)/T(3) + # some of these functions promote their result to double + # precision, but we want to check equality at precision T + rtol = Base.rtoldefault(real(T)) + for f in (:+, :-, :abs, :abs2, :conj, :inv, :sign, :acos, :acosh, :asin, :asinh, :atan, :atanh, :cos, :cosh, :exp10, :exp2, :exp, :expm1, :log10, :log1p, :log2, :log, :sin, :sinh, :sqrt, :tan, :tanh) - @test isapprox((@eval @fastmath $f($half)), (@eval $f($half))) - @test isapprox((@eval @fastmath $f($third)), (@eval $f($third))) + @test isapprox((@eval @fastmath $f($half)), (@eval $f($half)), rtol=rtol) + @test isapprox((@eval @fastmath $f($third)), (@eval $f($third)), rtol=rtol) end for f in (:+, :-, :*, :/, :(==), :!=, :^) @test isapprox((@eval @fastmath $f($half, $third)), - (@eval $f($half, $third))) + (@eval $f($half, $third)), rtol=rtol) @test isapprox((@eval @fastmath $f($third, $half)), - (@eval $f($third, $half))) + (@eval $f($third, $half)), rtol=rtol) end end diff --git a/test/floatapprox.jl b/test/floatapprox.jl index db969cb25f5634..d8ddf69d4e1188 100644 --- a/test/floatapprox.jl +++ b/test/floatapprox.jl @@ -43,3 +43,6 @@ # Notably missing from this test suite at the moment # * Tests for other magnitudes of numbers - very small, very big, and combinations of small and big # * Tests for various odd combinations of types, e.g. isapprox(x::Integer, y::Rational) + +# issue #12375: +@test !isapprox(1e17, 1)