From 020d8eec98beb65c251d6e78c08cc82eb4b29435 Mon Sep 17 00:00:00 2001 From: Per Rutquist Date: Wed, 22 May 2019 21:32:10 +0200 Subject: [PATCH] More methods for Irrational Define `zero(Irrational)` and `one(Irrational)` to return `false` and `true` respectively. (This is breaking if someone is currently using `true * pi` or `pi + false` as a way of forcing conversion to `Float64`.) Make `false` act as the additive identity for `Irrational` by defining a method for `+`. (Not type stable) Make `true` act as the multiplicative identity for `Irrational` by defining a method for `*` (Not type stable) Make `widemul` of two `Irrational`s return a `BigFloat` Make `sin(pi)` and `tan(pi)` return exactly 0.0 Define a number of methods on `Irrational` to default to conversion to Float64. --- base/irrationals.jl | 43 +++++++++++++++++++++++--- test/complex.jl | 2 +- test/numbers.jl | 75 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 110 insertions(+), 10 deletions(-) diff --git a/base/irrationals.jl b/base/irrationals.jl index 4b42aa9c05a26..87e8c913596a9 100644 --- a/base/irrationals.jl +++ b/base/irrationals.jl @@ -14,6 +14,12 @@ abstract type AbstractIrrational <: Real end Number type representing an exact irrational value denoted by the symbol `sym`. + +Note: `Irrational`s are converted to `Float64` before use by most +built-in mathematical operators. Conversion to high-precision types +such as `BigFloat` should be done before the first operation, +as in `2*big(π)` rather than `big(2π)`. The latter would only be +accurate to about 16 decimal places. """ struct Irrational{sym} <: AbstractIrrational end @@ -36,6 +42,7 @@ promote_rule(::Type{S}, ::Type{T}) where {S<:AbstractIrrational,T<:Number} = pro AbstractFloat(x::AbstractIrrational) = Float64(x) Float16(x::AbstractIrrational) = Float16(Float32(x)) Complex{T}(x::AbstractIrrational) where {T<:Real} = Complex{T}(T(x)) +Complex(x::AbstractIrrational, y::AbstractIrrational) = Complex(Float64(x), Float64(y)) @pure function Rational{T}(x::AbstractIrrational) where T<:Integer o = precision(BigFloat) @@ -136,14 +143,43 @@ hash(x::Irrational, h::UInt) = 3*objectid(x) - h widen(::Type{T}) where {T<:Irrational} = T +zero(::AbstractIrrational) = false +zero(::Type{<:AbstractIrrational}) = false + +one(::Type{<:AbstractIrrational}) = true + +oneunit(T::Type{<:AbstractIrrational}) = throw(ArgumentError("The number one cannot be of type $T")) +oneunit(x::T) where {T <: AbstractIrrational} = oneunit(T) + -(x::AbstractIrrational) = -Float64(x) -for op in Symbol[:+, :-, :*, :/, :^] +for op in Symbol[:+, :-, :*, :/, :^, :cld, :div, :divrem, :fld, :mod, :mod1, :rem, :UnitRange] @eval $op(x::AbstractIrrational, y::AbstractIrrational) = $op(Float64(x),Float64(y)) end -*(x::Bool, y::AbstractIrrational) = ifelse(x, Float64(y), 0.0) +*(x::Bool, y::AbstractIrrational) = x ? y : zero(y) +*(x::AbstractIrrational, y::Bool) = y * x ++(x::Bool, y::AbstractIrrational) = x ? 1.0 + y : y ++(x::AbstractIrrational, y::Bool) = y + x +-(x::AbstractIrrational, y::Bool) = y ? x - 1.0 : x round(x::Irrational, r::RoundingMode) = round(float(x), r) +Rational(x::AbstractIrrational) = rationalize(x) +rationalize(x::Irrational{T}) where T = throw(ArgumentError("Type must be specified. Use rationalize(Int, $T) to obtain a Rational{Int} approximation to the irrational constant $T.")) +Math.mod2pi(x::AbstractIrrational) = Float64(mod2pi(big(x))) +Math.rem2pi(x::AbstractIrrational, m) = Float64(rem2pi(big(x), m)) + +# sin(float(pi)) is computed from the difference between accurate pi and float(pi), +# but sin(pi) should return zero. (The same applies to tan(pi).) +sin(::Irrational{:π}) = 0.0 +tan(::Irrational{:π}) = 0.0 +Math.sincos(::Irrational{:π}) = (0.0, -1.0) + +sign(x::AbstractIrrational) = sign(Float64(x)) + +widemul(x::AbstractIrrational, y::AbstractIrrational) = big(x)*big(y) + +(::Colon)(x::AbstractIrrational, y::AbstractIrrational) = Float64(x):Float64(y) + """ @irrational sym val def @irrational(sym, val, def) @@ -188,6 +224,3 @@ function alignment(io::IO, x::AbstractIrrational) m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) : (length(m.captures[1]), length(m.captures[2])) end - -# inv -inv(x::AbstractIrrational) = 1/x diff --git a/test/complex.jl b/test/complex.jl index 0f419539a7027..512895ca6fcb2 100644 --- a/test/complex.jl +++ b/test/complex.jl @@ -991,7 +991,7 @@ end @testset "Complex Irrationals, issue #21204" begin for x in (pi, ℯ, Base.MathConstants.catalan) # No need to test all of them z = Complex(x, x) - @test typeof(z) == Complex{typeof(x)} + @test typeof(z) == Complex{Float64} @test exp(z) ≈ exp(x) * cis(x) @test log1p(z) ≈ log(1 + z) @test exp2(z) ≈ exp(z * log(2)) diff --git a/test/numbers.jl b/test/numbers.jl index 31761880f84c0..dafbb8215800c 100644 --- a/test/numbers.jl +++ b/test/numbers.jl @@ -2058,10 +2058,10 @@ for T = (UInt8,Int8,UInt16,Int16,UInt32,Int32,UInt64,Int64,UInt128,Int128) end @testset "Irrational/Bool multiplication" begin - @test false*pi === 0.0 - @test pi*false === 0.0 - @test true*pi === Float64(pi) - @test pi*true === Float64(pi) + @test false*pi === false + @test pi*false === false + @test true*pi === pi + @test pi*true === pi end # issue #5492 @test -0.0 + false === -0.0 @@ -2655,3 +2655,70 @@ end end end end + + +@testset "Irrational/Bool addition/subtraction" begin + @test false + pi === pi + @test pi + false === pi + @test true + pi === pi + 1.0 + @test pi + true === pi + 1.0 + + @test false - pi === -pi + @test pi - false === pi + @test true - pi === 1.0 - pi + @test pi - true === pi - 1.0 +end + +@testset "One-argument functions of Irrational" begin + for f in (*, +, abs, adjoint, conj, prod, real, sum) + @test f(pi) === pi + end + for f in (isfinite, isreal, one) + @test f(pi) === true + end + for f in (imag, isinf, isinteger, ismissing, isnan, isnothing, isone, + iszero, signbit, zero) + @test f(pi) === false + end + for f in (sin, tan) + @test f(float(pi)) < eps() # otherwise f should not be on this list + # f(Float64(pi)) is supposed to be different from zero, but f(pi) is not. + @test f(pi) === 0.0 + end + for f in (cot, csc) + @test f(pi) === Inf + end + @test cis(pi) === -1.0 + 0.0im + @test sincos(pi) === (sin(pi), cos(pi)) + for f in (-, abs2, acosh, acot, acotd, acoth, acsc, acscd, acsch, angle, + asec, asecd, asinh, atan, atand, cbrt, ceil, cos, cosc, cosd, + cosh, cospi, cotd, coth, cscd, csch, deg2rad, exp, exp10, exp2, + expm1, floor, hypot, inv, log, log10, log1p, log2, rad2deg, + round, sec, secd, sech, sign, sinc, sind, sinh, sinpi, sqrt, + tand, tanh, trunc) + @test f(pi) isa Float64 + @test isapprox(f(pi), Float64(f(big(pi)))) + end + @test reim(pi) === (pi, false) + @test_throws ArgumentError oneunit(pi) +end + +@testset "Two-argument functions of Irrationals" begin + for i in (ℯ, pi), j in (ℯ, π) + for f in (Complex, ComplexF16, ComplexF32, ComplexF64, + cld, cmp, complex, fld, + isapprox, isequal, isless, issetequal, issubset) + @test f(i, j) === f(float(i), float(j)) + end + for f in (atan, atand, copysign, div, fld, flipsign, + hypot, kron, log, max, min, + mod, mod1, nextpow, prevpow, rem) + @test isapprox(f(i, j), Float64(f(big(i), big(j)))) + end + for f in (divrem, fldmod, minmax) + @test all(isapprox.(f(i, j), Float64.(f(big(i), big(j))))) + end + @test isapprox(widemul(i, j), big(i)*big(j)) + @test typeof(widemul(i, j)) == typeof(widemul(float(i), float(j))) + end +end