From 38dcf8aa4ac62a8aabcf12dbf6e87b7ad171c23c Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Thu, 16 Apr 2020 12:03:20 +0200 Subject: [PATCH 01/10] Faster Rationals by avoiding unnecessary divgcd --- base/rational.jl | 98 +++++++++++++++++++++++++++++++++--------------- test/rational.jl | 8 ++++ 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index c56eecff3a031..f286506d5570c 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -20,13 +20,32 @@ struct Rational{T<:Integer} <: Real end return new(num2, den2) end + + function Rational{T}(num::Integer, den::Integer, ::Val{false}) where T<:Integer + # Used when num and den are only known to be coprime and not both equal to 0 + if T<:Signed && signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(T) + num = -num + end + return new(num, den) + end + + function Rational{T}(num::Integer, den::Integer, ::Val{true}) where T<:Integer + # Used to skip all checks. This means we know num and den are coprime + # and cannot both be 0 and den >= 0 + return new(num, den) + end + end @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n,d) +Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n, d) +Rational(n::T, d::T, check::Val{b}) where {T<:Integer, b} = Rational{T}(n, d, check) Rational(n::Integer, d::Integer) = Rational(promote(n,d)...) -Rational(n::Integer) = Rational(n,one(n)) +Rational(n::Integer, d::Integer, check::Val{b}) where {b} = Rational(promote(n, d)..., check) +Rational(n::Integer) = Rational(n, one(n), Val(true)) function divgcd(x::Integer,y::Integer) g = gcd(x,y) @@ -51,19 +70,19 @@ julia> (3 // 5) // (2 // 1) function //(x::Rational, y::Integer) xn,yn = divgcd(x.num,y) - xn//checked_mul(x.den,yn) + Rational(xn, checked_mul(x.den, yn), Val(false)) end function //(x::Integer, y::Rational) xn,yn = divgcd(x,y.num) - checked_mul(xn,y.den)//yn + Rational(checked_mul(xn, y.den), yn, Val(false)) end function //(x::Rational, y::Rational) xn,yn = divgcd(x.num,y.num) xd,yd = divgcd(x.den,y.den) - checked_mul(xn,yd)//checked_mul(xd,yn) + Rational(checked_mul(xn, yd), checked_mul(xd, yn), Val(false)) end -//(x::Complex, y::Real) = complex(real(x)//y,imag(x)//y) +//(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y) //(x::Number, y::Complex) = x*conj(y)//abs2(y) @@ -84,8 +103,8 @@ function write(s::IO, z::Rational) write(s,numerator(z),denominator(z)) end -Rational{T}(x::Rational) where {T<:Integer} = Rational{T}(convert(T,x.num), convert(T,x.den)) -Rational{T}(x::Integer) where {T<:Integer} = Rational{T}(convert(T,x), convert(T,1)) +Rational{T}(x::Rational) where {T<:Integer} = Rational{T}(convert(T,x.num), convert(T,x.den), Val(true)) +Rational{T}(x::Integer) where {T<:Integer} = Rational{T}(convert(T,x), one(T), Val(true)) Rational(x::Rational) = x @@ -108,7 +127,7 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) -big(q::Rational) = big(numerator(q))//big(denominator(q)) +big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)), Val(true)) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) @@ -140,8 +159,11 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer if tol < 0 throw(ArgumentError("negative tolerance $tol")) end + if T<:Unsigned && x < 0 + throw(OverflowError("cannot negate unsigned number")) + end isnan(x) && return T(x)//one(T) - isinf(x) && return (x < 0 ? -one(T) : one(T))//zero(T) + isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T), Val(true)) p, q = (x < 0 ? -one(T) : one(T)), zero(T) pp, qq = zero(T), one(T) @@ -234,30 +256,30 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = copysign(x.num,y) // x.den -copysign(x::Rational, y::Rational) = copysign(x.num,y.num) // x.den +copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den, Val(true)) +copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den, Val(true)) abs(x::Rational) = Rational(abs(x.num), x.den) -typemin(::Type{Rational{T}}) where {T<:Integer} = -one(T)//zero(T) -typemax(::Type{Rational{T}}) where {T<:Integer} = one(T)//zero(T) +typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T), Val(true)) +typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T), Val(true)) +typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T), Val(true)) isinteger(x::Rational) = x.den == 1 -+(x::Rational) = (+x.num) // x.den --(x::Rational) = (-x.num) // x.den ++(x::Rational) = Rational(+x.num, x.den, Val(true)) +-(x::Rational) = Rational(-x.num, x.den, Val(true)) function -(x::Rational{T}) where T<:BitSigned x.num == typemin(T) && throw(OverflowError("rational numerator is typemin(T)")) - (-x.num) // x.den + Rational(-x.num, x.den, Val(true)) end function -(x::Rational{T}) where T<:Unsigned x.num != zero(T) && throw(OverflowError("cannot negate unsigned number")) x end -for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), - (:rem,:rem), (:mod,:mod)) +for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), (:rem,:rem), (:mod,:mod)) @eval begin function ($op)(x::Rational, y::Rational) xd, yd = divgcd(x.den, y.den) @@ -265,9 +287,19 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), end function ($op)(x::Rational, y::Integer) - Rational(($chop)(x.num, checked_mul(x.den, y)), x.den) + Rational(($chop)(x.num, checked_mul(x.den, y)), x.den, Val(true)) end - + end +end +for (op,chop) in ((:+,:checked_add), (:-,:checked_sub)) + @eval begin + function ($op)(y::Integer, x::Rational) + Rational(($chop)(checked_mul(x.den, y), x.num), x.den, Val(true)) + end + end +end +for (op,chop) in ((:rem,:rem), (:mod,:mod)) + @eval begin function ($op)(y::Integer, x::Rational) Rational(($chop)(checked_mul(x.den, y), x.num), x.den) end @@ -275,18 +307,22 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), end function *(x::Rational, y::Rational) - xn,yd = divgcd(x.num,y.den) - xd,yn = divgcd(x.den,y.num) - checked_mul(xn,yn) // checked_mul(xd,yd) + xn, yd = divgcd(x.num, y.den) + xd, yn = divgcd(x.den, y.num) + Rational(checked_mul(xn, yn), checked_mul(xd, yd), Val(true)) end function *(x::Rational, y::Integer) xd, yn = divgcd(x.den, y) - checked_mul(x.num, yn) // xd + Rational(checked_mul(x.num, yn), xd, Val(true)) +end +function *(y::Integer, x::Rational) + yn, xd = divgcd(y, x.den) + Rational(checked_mul(yn, x.num), xd, Val(true)) end -*(x::Integer, y::Rational) = *(y, x) -/(x::Rational, y::Rational) = x//y +/(x::Rational, y::Union{Rational, Integer}) = x//y +/(x::Integer, y::Rational) = x//y /(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y -inv(x::Rational) = Rational(x.den, x.num) +inv(x::Rational) = Rational(x.den, x.num, Val(false)) fma(x::Rational, y::Rational, z::Rational) = x*y+z @@ -403,7 +439,7 @@ round(x::Rational, r::RoundingMode=RoundNearest) = round(typeof(x), x, r) function round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T,Tr} if iszero(denominator(x)) && !(T <: Integer) - return convert(T, copysign(one(Tr)//zero(Tr), numerator(x))) + return convert(T, copysign(Rational(one(Tr), zero(Tr), Val(true)), numerator(x))) end convert(T, div(numerator(x), denominator(x), r)) end @@ -437,8 +473,8 @@ end float(::Type{Rational{T}}) where {T<:Integer} = float(T) -gcd(x::Rational, y::Rational) = gcd(x.num, y.num) // lcm(x.den, y.den) -lcm(x::Rational, y::Rational) = lcm(x.num, y.num) // gcd(x.den, y.den) +gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den), Val(true)) +lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den), Val(true)) function gcdx(x::Rational, y::Rational) c = gcd(x, y) if iszero(c.num) diff --git a/test/rational.jl b/test/rational.jl index 19913f0f7dab9..bb446840e7d29 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -17,6 +17,7 @@ using Test @test 5//0 == 1//0 @test -1//0 == -1//0 @test -7//0 == -1//0 + @test (-1//2) // (-2//5) == 5//4 @test_throws OverflowError -(0x01//0x0f) @test_throws OverflowError -(typemin(Int)//1) @@ -26,9 +27,12 @@ using Test @test (typemax(Int)//1) / (typemax(Int)//1) == 1 @test (1//typemax(Int)) / (1//typemax(Int)) == 1 @test_throws OverflowError (1//2)^63 + @test inv((1+typemin(Int))//typemax(Int)) == -1 + @test_throws ArgumentError inv(typemin(Int)//typemax(Int)) @test @inferred(rationalize(Int, 3.0, 0.0)) === 3//1 @test @inferred(rationalize(Int, 3.0, 0)) === 3//1 + @test_throws OverflowError rationalize(UInt, -2.0) @test_throws ArgumentError rationalize(Int, big(3.0), -1.) # issue 26823 @test_throws InexactError rationalize(Int, NaN) @@ -120,6 +124,8 @@ end @test widen(Rational{T}) == Rational{widen(T)} end + @test iszero(typemin(Rational{UInt})) + @test Rational(Float32(rand_int)) == Rational(rand_int) @test Rational(Rational(rand_int)) == Rational(rand_int) @@ -548,6 +554,8 @@ end end @test 1//2 * 3 == 3//2 @test -3 * (1//2) == -3//2 + @test (6//5) // -3 == -2//5 + @test -4 // (-6//5) == 10//3 @test_throws OverflowError UInt(1)//2 - 1 @test_throws OverflowError 1 - UInt(5)//2 From 5527cb93d2a87529a497327fc97053f1c197e65f Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Wed, 22 Apr 2020 18:01:25 +0200 Subject: [PATCH 02/10] Refactor constructors using unsafe_rational --- base/rational.jl | 105 +++++++++++++++++++++++++---------------------- 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index f286506d5570c..fecaa45f26e31 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -1,5 +1,22 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +""" + unsafe_rational(num::Integer, den::Integer; checksign=false) + unsafe_rational{T<:Integer}(num::Integer, den::Integer; checksign::Bool=false) + +Create a `Rational` with numerator `num` and denominator `den`. + +The `unsafe` prefix on this function indicates that no check is performed on +`num` and `den` to ensure that the resulting `Rational` is well-formed. +A `Rational` is well-formed if its numerator and denominator are coprime and if +the denominator is non-negative. + +`checksign` optionally specifies whether to check the sign of `den`. + +Ill-formed `Rational`s result in undefined behaviour. +""" +struct unsafe_rational{T<:Integer} <: Function end + """ Rational{T<:Integer} <: Real @@ -10,42 +27,30 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer) where T<:Integer - num == den == zero(T) && __throw_rational_argerror_zero(T) - num2, den2 = divgcd(num, den) - if T<:Signed && signbit(den2) - den2 = -den2 - signbit(den2) && __throw_rational_argerror_typemin(T) - num2 = -num2 - end - return new(num2, den2) - end - - function Rational{T}(num::Integer, den::Integer, ::Val{false}) where T<:Integer - # Used when num and den are only known to be coprime and not both equal to 0 - if T<:Signed && signbit(den) + function (::Type{unsafe_rational{T}})(num::Integer, den::Integer; checksign::Bool=false) where T<:Integer + if checksign && T<:Signed && signbit(den) den = -den signbit(den) && __throw_rational_argerror_typemin(T) num = -num end - return new(num, den) - end - - function Rational{T}(num::Integer, den::Integer, ::Val{true}) where T<:Integer - # Used to skip all checks. This means we know num and den are coprime - # and cannot both be 0 and den >= 0 - return new(num, den) + return new{T}(num, den) end - end + @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) +function Rational{T}(num::Integer, den::Integer) where T<:Integer + num == den == zero(T) && __throw_rational_argerror_zero(T) + num2, den2 = divgcd(num, den) + return unsafe_rational{T}(num2, den2; checksign=true) +end + Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n, d) -Rational(n::T, d::T, check::Val{b}) where {T<:Integer, b} = Rational{T}(n, d, check) -Rational(n::Integer, d::Integer) = Rational(promote(n,d)...) -Rational(n::Integer, d::Integer, check::Val{b}) where {b} = Rational(promote(n, d)..., check) -Rational(n::Integer) = Rational(n, one(n), Val(true)) +Rational(n::Integer, d::Integer) = Rational(promote(n, d)...) +unsafe_rational(n::T, d::T; checksign=false) where {T<:Integer} = unsafe_rational{T}(n, d; checksign) +unsafe_rational(n::Integer, d::Integer; checksign=false) = unsafe_rational(promote(n, d)...; checksign) +Rational(n::Integer) = unsafe_rational(n, one(n)) function divgcd(x::Integer,y::Integer) g = gcd(x,y) @@ -70,16 +75,16 @@ julia> (3 // 5) // (2 // 1) function //(x::Rational, y::Integer) xn,yn = divgcd(x.num,y) - Rational(xn, checked_mul(x.den, yn), Val(false)) + unsafe_rational(xn, checked_mul(x.den, yn); checksign=true) end function //(x::Integer, y::Rational) xn,yn = divgcd(x,y.num) - Rational(checked_mul(xn, y.den), yn, Val(false)) + unsafe_rational(checked_mul(xn, y.den), yn; checksign=true) end function //(x::Rational, y::Rational) xn,yn = divgcd(x.num,y.num) xd,yd = divgcd(x.den,y.den) - Rational(checked_mul(xn, yd), checked_mul(xd, yn), Val(false)) + unsafe_rational(checked_mul(xn, yd), checked_mul(xd, yn); checksign=true) end //(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y) @@ -103,8 +108,8 @@ function write(s::IO, z::Rational) write(s,numerator(z),denominator(z)) end -Rational{T}(x::Rational) where {T<:Integer} = Rational{T}(convert(T,x.num), convert(T,x.den), Val(true)) -Rational{T}(x::Integer) where {T<:Integer} = Rational{T}(convert(T,x), one(T), Val(true)) +Rational{T}(x::Rational) where {T<:Integer} = unsafe_rational{T}(convert(T,x.num), convert(T,x.den)) +Rational{T}(x::Integer) where {T<:Integer} = unsafe_rational{T}(convert(T,x), one(T)) Rational(x::Rational) = x @@ -127,7 +132,7 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) -big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)), Val(true)) +big(q::Rational) = unsafe_rational(big(numerator(q)), big(denominator(q))) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) @@ -163,7 +168,7 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer throw(OverflowError("cannot negate unsigned number")) end isnan(x) && return T(x)//one(T) - isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T), Val(true)) + isinf(x) && return unsafe_rational(x < 0 ? -one(T) : one(T), zero(T)) p, q = (x < 0 ? -one(T) : one(T)), zero(T) pp, qq = zero(T), one(T) @@ -256,23 +261,23 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den, Val(true)) -copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den, Val(true)) +copysign(x::Rational, y::Real) = unsafe_rational(copysign(x.num, y), x.den) +copysign(x::Rational, y::Rational) = unsafe_rational(copysign(x.num, y.num), x.den) abs(x::Rational) = Rational(abs(x.num), x.den) -typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T), Val(true)) -typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T), Val(true)) -typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T), Val(true)) +typemin(::Type{Rational{T}}) where {T<:Signed} = unsafe_rational(-one(T), zero(T)) +typemin(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(zero(T), one(T)) +typemax(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(one(T), zero(T)) isinteger(x::Rational) = x.den == 1 -+(x::Rational) = Rational(+x.num, x.den, Val(true)) --(x::Rational) = Rational(-x.num, x.den, Val(true)) ++(x::Rational) = unsafe_rational(+x.num, x.den) +-(x::Rational) = unsafe_rational(-x.num, x.den) function -(x::Rational{T}) where T<:BitSigned x.num == typemin(T) && throw(OverflowError("rational numerator is typemin(T)")) - Rational(-x.num, x.den, Val(true)) + unsafe_rational(-x.num, x.den) end function -(x::Rational{T}) where T<:Unsigned x.num != zero(T) && throw(OverflowError("cannot negate unsigned number")) @@ -287,14 +292,14 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), (:rem,:rem), (:mod,:mod) end function ($op)(x::Rational, y::Integer) - Rational(($chop)(x.num, checked_mul(x.den, y)), x.den, Val(true)) + unsafe_rational(($chop)(x.num, checked_mul(x.den, y)), x.den) end end end for (op,chop) in ((:+,:checked_add), (:-,:checked_sub)) @eval begin function ($op)(y::Integer, x::Rational) - Rational(($chop)(checked_mul(x.den, y), x.num), x.den, Val(true)) + unsafe_rational(($chop)(checked_mul(x.den, y), x.num), x.den) end end end @@ -309,20 +314,20 @@ end function *(x::Rational, y::Rational) xn, yd = divgcd(x.num, y.den) xd, yn = divgcd(x.den, y.num) - Rational(checked_mul(xn, yn), checked_mul(xd, yd), Val(true)) + unsafe_rational(checked_mul(xn, yn), checked_mul(xd, yd)) end function *(x::Rational, y::Integer) xd, yn = divgcd(x.den, y) - Rational(checked_mul(x.num, yn), xd, Val(true)) + unsafe_rational(checked_mul(x.num, yn), xd) end function *(y::Integer, x::Rational) yn, xd = divgcd(y, x.den) - Rational(checked_mul(yn, x.num), xd, Val(true)) + unsafe_rational(checked_mul(yn, x.num), xd) end /(x::Rational, y::Union{Rational, Integer}) = x//y /(x::Integer, y::Rational) = x//y /(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y -inv(x::Rational) = Rational(x.den, x.num, Val(false)) +inv(x::Rational) = unsafe_rational(x.den, x.num; checksign=true) fma(x::Rational, y::Rational, z::Rational) = x*y+z @@ -439,7 +444,7 @@ round(x::Rational, r::RoundingMode=RoundNearest) = round(typeof(x), x, r) function round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T,Tr} if iszero(denominator(x)) && !(T <: Integer) - return convert(T, copysign(Rational(one(Tr), zero(Tr), Val(true)), numerator(x))) + return convert(T, copysign(unsafe_rational(one(Tr), zero(Tr)), numerator(x))) end convert(T, div(numerator(x), denominator(x), r)) end @@ -473,8 +478,8 @@ end float(::Type{Rational{T}}) where {T<:Integer} = float(T) -gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den), Val(true)) -lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den), Val(true)) +gcd(x::Rational, y::Rational) = unsafe_rational(gcd(x.num, y.num), lcm(x.den, y.den)) +lcm(x::Rational, y::Rational) = unsafe_rational(lcm(x.num, y.num), gcd(x.den, y.den)) function gcdx(x::Rational, y::Rational) c = gcd(x, y) if iszero(c.num) From 571a1ac6a8e73b9b6f091b38fc24879f6ddab3a6 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Wed, 22 Apr 2020 23:14:52 +0200 Subject: [PATCH 03/10] Refactor unsafe_rational into Rational with keyword arguments --- base/rational.jl | 112 +++++++++++++++++++++++++---------------------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index fecaa45f26e31..c8989059cc445 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -1,22 +1,5 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -""" - unsafe_rational(num::Integer, den::Integer; checksign=false) - unsafe_rational{T<:Integer}(num::Integer, den::Integer; checksign::Bool=false) - -Create a `Rational` with numerator `num` and denominator `den`. - -The `unsafe` prefix on this function indicates that no check is performed on -`num` and `den` to ensure that the resulting `Rational` is well-formed. -A `Rational` is well-formed if its numerator and denominator are coprime and if -the denominator is non-negative. - -`checksign` optionally specifies whether to check the sign of `den`. - -Ill-formed `Rational`s result in undefined behaviour. -""" -struct unsafe_rational{T<:Integer} <: Function end - """ Rational{T<:Integer} <: Real @@ -27,8 +10,12 @@ struct Rational{T<:Integer} <: Real num::T den::T - function (::Type{unsafe_rational{T}})(num::Integer, den::Integer; checksign::Bool=false) where T<:Integer - if checksign && T<:Signed && signbit(den) + function Rational{T}(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) where T<:Integer + if safe + num == den == zero(T) && __throw_rational_argerror_zero(T) + num, den = divgcd(num, den) + end + if T<:Signed && checksign && signbit(den) den = -den signbit(den) && __throw_rational_argerror_typemin(T) num = -num @@ -36,21 +23,36 @@ struct Rational{T<:Integer} <: Real return new{T}(num, den) end end - @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -function Rational{T}(num::Integer, den::Integer) where T<:Integer - num == den == zero(T) && __throw_rational_argerror_zero(T) - num2, den2 = divgcd(num, den) - return unsafe_rational{T}(num2, den2; checksign=true) +function Rational(n::T, d::T; safe=true, checksign=safe) where T<:Integer + Rational{T}(n, d; safe, checksign) end -Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n, d) -Rational(n::Integer, d::Integer) = Rational(promote(n, d)...) -unsafe_rational(n::T, d::T; checksign=false) where {T<:Integer} = unsafe_rational{T}(n, d; checksign) -unsafe_rational(n::Integer, d::Integer; checksign=false) = unsafe_rational(promote(n, d)...; checksign) -Rational(n::Integer) = unsafe_rational(n, one(n)) +""" + Rational(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) + Rational{T<:Integer}(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) + +Create a `Rational` with numerator `num` and denominator `den`. + +A `Rational` is well-formed if its numerator and denominator are coprime and if the +denominator is non-negative. By default, the constructor checks its arguments so that +the created `Rational` is well-formed. Well-formedness is kept across operations on +`Rational`s. + +Unset the optional argument `checksign` to bypass the check on the sign of `den`.\\ +Unset the optional argument `safe` to bypass the coprimality check. By default, unsetting +`safe` also unsets `checksign`. + +!!! warning + Using an ill-formed `Rational` result in undefined behavior. Only bypass checks if + `num` and `den` are known to satisfy them. +""" +function Rational(n::Integer, d::Integer; safe=true, checksign=safe) + Rational(promote(n, d)...; safe, checksign) +end +Rational(n::Integer) = Rational(n, one(n); safe=false) function divgcd(x::Integer,y::Integer) g = gcd(x,y) @@ -75,16 +77,16 @@ julia> (3 // 5) // (2 // 1) function //(x::Rational, y::Integer) xn,yn = divgcd(x.num,y) - unsafe_rational(xn, checked_mul(x.den, yn); checksign=true) + Rational(xn, checked_mul(x.den, yn); safe=false, checksign=true) end function //(x::Integer, y::Rational) xn,yn = divgcd(x,y.num) - unsafe_rational(checked_mul(xn, y.den), yn; checksign=true) + Rational(checked_mul(xn, y.den), yn; safe=false, checksign=true) end function //(x::Rational, y::Rational) xn,yn = divgcd(x.num,y.num) xd,yd = divgcd(x.den,y.den) - unsafe_rational(checked_mul(xn, yd), checked_mul(xd, yn); checksign=true) + Rational(checked_mul(xn, yd), checked_mul(xd, yn); safe=false, checksign=true) end //(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y) @@ -108,8 +110,12 @@ function write(s::IO, z::Rational) write(s,numerator(z),denominator(z)) end -Rational{T}(x::Rational) where {T<:Integer} = unsafe_rational{T}(convert(T,x.num), convert(T,x.den)) -Rational{T}(x::Integer) where {T<:Integer} = unsafe_rational{T}(convert(T,x), one(T)) +function Rational{T}(x::Rational) where T<:Integer + Rational{T}(convert(T, x.num), convert(T,x.den); safe=false) +end +function Rational{T}(x::Integer) where T<:Integer + Rational{T}(convert(T, x), one(T); safe=false) +end Rational(x::Rational) = x @@ -132,7 +138,7 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) -big(q::Rational) = unsafe_rational(big(numerator(q)), big(denominator(q))) +big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)); safe=false) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) @@ -168,7 +174,7 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer throw(OverflowError("cannot negate unsigned number")) end isnan(x) && return T(x)//one(T) - isinf(x) && return unsafe_rational(x < 0 ? -one(T) : one(T), zero(T)) + isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T); safe=false) p, q = (x < 0 ? -one(T) : one(T)), zero(T) pp, qq = zero(T), one(T) @@ -261,23 +267,23 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = unsafe_rational(copysign(x.num, y), x.den) -copysign(x::Rational, y::Rational) = unsafe_rational(copysign(x.num, y.num), x.den) +copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den; safe=false) +copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den; safe=false) abs(x::Rational) = Rational(abs(x.num), x.den) -typemin(::Type{Rational{T}}) where {T<:Signed} = unsafe_rational(-one(T), zero(T)) -typemin(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(zero(T), one(T)) -typemax(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(one(T), zero(T)) +typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T); safe=false) +typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T); safe=false) +typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T); safe=false) isinteger(x::Rational) = x.den == 1 -+(x::Rational) = unsafe_rational(+x.num, x.den) --(x::Rational) = unsafe_rational(-x.num, x.den) ++(x::Rational) = Rational(+x.num, x.den; safe=false) +-(x::Rational) = Rational(-x.num, x.den; safe=false) function -(x::Rational{T}) where T<:BitSigned x.num == typemin(T) && throw(OverflowError("rational numerator is typemin(T)")) - unsafe_rational(-x.num, x.den) + Rational(-x.num, x.den; safe=false) end function -(x::Rational{T}) where T<:Unsigned x.num != zero(T) && throw(OverflowError("cannot negate unsigned number")) @@ -292,14 +298,14 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), (:rem,:rem), (:mod,:mod) end function ($op)(x::Rational, y::Integer) - unsafe_rational(($chop)(x.num, checked_mul(x.den, y)), x.den) + Rational(($chop)(x.num, checked_mul(x.den, y)), x.den; safe=false) end end end for (op,chop) in ((:+,:checked_add), (:-,:checked_sub)) @eval begin function ($op)(y::Integer, x::Rational) - unsafe_rational(($chop)(checked_mul(x.den, y), x.num), x.den) + Rational(($chop)(checked_mul(x.den, y), x.num), x.den; safe=false) end end end @@ -314,20 +320,20 @@ end function *(x::Rational, y::Rational) xn, yd = divgcd(x.num, y.den) xd, yn = divgcd(x.den, y.num) - unsafe_rational(checked_mul(xn, yn), checked_mul(xd, yd)) + Rational(checked_mul(xn, yn), checked_mul(xd, yd); safe=false) end function *(x::Rational, y::Integer) xd, yn = divgcd(x.den, y) - unsafe_rational(checked_mul(x.num, yn), xd) + Rational(checked_mul(x.num, yn), xd; safe=false) end function *(y::Integer, x::Rational) yn, xd = divgcd(y, x.den) - unsafe_rational(checked_mul(yn, x.num), xd) + Rational(checked_mul(yn, x.num), xd; safe=false) end /(x::Rational, y::Union{Rational, Integer}) = x//y /(x::Integer, y::Rational) = x//y /(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y -inv(x::Rational) = unsafe_rational(x.den, x.num; checksign=true) +inv(x::Rational) = Rational(x.den, x.num; safe=false, checksign=true) fma(x::Rational, y::Rational, z::Rational) = x*y+z @@ -444,7 +450,7 @@ round(x::Rational, r::RoundingMode=RoundNearest) = round(typeof(x), x, r) function round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T,Tr} if iszero(denominator(x)) && !(T <: Integer) - return convert(T, copysign(unsafe_rational(one(Tr), zero(Tr)), numerator(x))) + return convert(T, copysign(Rational(one(Tr), zero(Tr); safe=false), numerator(x))) end convert(T, div(numerator(x), denominator(x), r)) end @@ -478,8 +484,8 @@ end float(::Type{Rational{T}}) where {T<:Integer} = float(T) -gcd(x::Rational, y::Rational) = unsafe_rational(gcd(x.num, y.num), lcm(x.den, y.den)) -lcm(x::Rational, y::Rational) = unsafe_rational(lcm(x.num, y.num), gcd(x.den, y.den)) +gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den); safe=false) +lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den); safe=false) function gcdx(x::Rational, y::Rational) c = gcd(x, y) if iszero(c.num) From 856c5948db8a7fa5f4030f0fa3e964629a1e49ec Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Thu, 23 Apr 2020 21:34:34 +0200 Subject: [PATCH 04/10] Only keep one keyword argument for the constructor --- base/rational.jl | 66 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index c8989059cc445..918cfb9e5d016 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -10,15 +10,15 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) where T<:Integer + function Rational{T}(num::Integer, den::Integer; safe::Bool=true) where T<:Integer if safe num == den == zero(T) && __throw_rational_argerror_zero(T) num, den = divgcd(num, den) - end - if T<:Signed && checksign && signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(T) - num = -num + if T<:Signed && signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(T) + num = -num + end end return new{T}(num, den) end @@ -26,13 +26,13 @@ end @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -function Rational(n::T, d::T; safe=true, checksign=safe) where T<:Integer - Rational{T}(n, d; safe, checksign) +function Rational(n::T, d::T; safe=true) where T<:Integer + Rational{T}(n, d; safe) end """ - Rational(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) - Rational{T<:Integer}(num::Integer, den::Integer; safe::Bool=true, checksign::Bool=safe) + Rational(num::Integer, den::Integer; safe::Bool=true) + Rational{T<:Integer}(num::Integer, den::Integer; safe::Bool=true) Create a `Rational` with numerator `num` and denominator `den`. @@ -41,16 +41,14 @@ denominator is non-negative. By default, the constructor checks its arguments so the created `Rational` is well-formed. Well-formedness is kept across operations on `Rational`s. -Unset the optional argument `checksign` to bypass the check on the sign of `den`.\\ -Unset the optional argument `safe` to bypass the coprimality check. By default, unsetting -`safe` also unsets `checksign`. +Unset the optional argument `safe` to bypass all checks. !!! warning Using an ill-formed `Rational` result in undefined behavior. Only bypass checks if `num` and `den` are known to satisfy them. """ -function Rational(n::Integer, d::Integer; safe=true, checksign=safe) - Rational(promote(n, d)...; safe, checksign) +function Rational(n::Integer, d::Integer; safe=true) + Rational(promote(n, d)...; safe) end Rational(n::Integer) = Rational(n, one(n); safe=false) @@ -76,17 +74,35 @@ julia> (3 // 5) // (2 // 1) //(n::Integer, d::Integer) = Rational(n,d) function //(x::Rational, y::Integer) - xn,yn = divgcd(x.num,y) - Rational(xn, checked_mul(x.den, yn); safe=false, checksign=true) + xn, yn = divgcd(x.num,y) + num, den = promote(xn, checked_mul(x.den, yn)) + if signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(typeof(den)) + num = -num + end + Rational(num, den; safe=false) end function //(x::Integer, y::Rational) - xn,yn = divgcd(x,y.num) - Rational(checked_mul(xn, y.den), yn; safe=false, checksign=true) + xn, yn = divgcd(x,y.num) + num, den = promote(checked_mul(xn, y.den), yn) + if signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(typeof(den)) + num = -num + end + Rational(num, den; safe=false) end function //(x::Rational, y::Rational) xn,yn = divgcd(x.num,y.num) xd,yd = divgcd(x.den,y.den) - Rational(checked_mul(xn, yd), checked_mul(xd, yn); safe=false, checksign=true) + num, den = promote(checked_mul(xn, yd), checked_mul(xd, yn)) + if signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(typeof(den)) + num = -num + end + Rational(num, den; safe=false) end //(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y) @@ -333,7 +349,15 @@ end /(x::Rational, y::Union{Rational, Integer}) = x//y /(x::Integer, y::Rational) = x//y /(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y -inv(x::Rational) = Rational(x.den, x.num; safe=false, checksign=true) +function inv(x::Rational{T}) where T + num, den = x.den, x.num + if signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(T) + num = -num + end + Rational(num, den; safe=false) +end fma(x::Rational, y::Rational, z::Rational) = x*y+z From 270bfaa0e410f12ecdca7cbdb936c48fc9630172 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Fri, 24 Apr 2020 00:59:07 +0200 Subject: [PATCH 05/10] Rename safe -> checked and refactor sign check into a function checked_den --- base/rational.jl | 112 +++++++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 63 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index 918cfb9e5d016..5e008b2dcc3ac 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -10,15 +10,11 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer; safe::Bool=true) where T<:Integer - if safe + function Rational{T}(num::Integer, den::Integer; checked::Bool=true) where T<:Integer + if checked num == den == zero(T) && __throw_rational_argerror_zero(T) num, den = divgcd(num, den) - if T<:Signed && signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(T) - num = -num - end + num, den = _checked_den(num, den) end return new{T}(num, den) end @@ -26,13 +22,27 @@ end @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -function Rational(n::T, d::T; safe=true) where T<:Integer - Rational{T}(n, d; safe) +function _checked_den(num::T, den::T) where T<:Integer + if signbit(den) + den = -den + signbit(den) && __throw_rational_argerror_typemin(T) + num = -num + end + return num, den +end + +function checked_den(num::T, den::T) where T<:Integer + Rational{T}(_checked_den(num, den)...; checked=false) +end +checked_den(num::Integer, den::Integer) = checked_den(promote(num, den)...) + +function Rational(n::T, d::T; checked=true) where T<:Integer + Rational{T}(n, d; checked) end """ - Rational(num::Integer, den::Integer; safe::Bool=true) - Rational{T<:Integer}(num::Integer, den::Integer; safe::Bool=true) + Rational(num::Integer, den::Integer; checked::Bool=true) + Rational{T<:Integer}(num::Integer, den::Integer; checked::Bool=true) Create a `Rational` with numerator `num` and denominator `den`. @@ -41,16 +51,16 @@ denominator is non-negative. By default, the constructor checks its arguments so the created `Rational` is well-formed. Well-formedness is kept across operations on `Rational`s. -Unset the optional argument `safe` to bypass all checks. +Unset the optional argument `checked` to bypass all checks. !!! warning Using an ill-formed `Rational` result in undefined behavior. Only bypass checks if `num` and `den` are known to satisfy them. """ -function Rational(n::Integer, d::Integer; safe=true) - Rational(promote(n, d)...; safe) +function Rational(n::Integer, d::Integer; checked=true) + Rational(promote(n, d)...; checked) end -Rational(n::Integer) = Rational(n, one(n); safe=false) +Rational(n::Integer) = Rational(n, one(n); checked=false) function divgcd(x::Integer,y::Integer) g = gcd(x,y) @@ -75,34 +85,16 @@ julia> (3 // 5) // (2 // 1) function //(x::Rational, y::Integer) xn, yn = divgcd(x.num,y) - num, den = promote(xn, checked_mul(x.den, yn)) - if signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(typeof(den)) - num = -num - end - Rational(num, den; safe=false) + checked_den(xn, checked_mul(x.den, yn)) end function //(x::Integer, y::Rational) xn, yn = divgcd(x,y.num) - num, den = promote(checked_mul(xn, y.den), yn) - if signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(typeof(den)) - num = -num - end - Rational(num, den; safe=false) + checked_den(checked_mul(xn, y.den), yn) end function //(x::Rational, y::Rational) xn,yn = divgcd(x.num,y.num) xd,yd = divgcd(x.den,y.den) - num, den = promote(checked_mul(xn, yd), checked_mul(xd, yn)) - if signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(typeof(den)) - num = -num - end - Rational(num, den; safe=false) + checked_den(checked_mul(xn, yd), checked_mul(xd, yn)) end //(x::Complex, y::Real) = complex(real(x)//y, imag(x)//y) @@ -127,10 +119,10 @@ function write(s::IO, z::Rational) end function Rational{T}(x::Rational) where T<:Integer - Rational{T}(convert(T, x.num), convert(T,x.den); safe=false) + Rational{T}(convert(T, x.num), convert(T,x.den); checked=false) end function Rational{T}(x::Integer) where T<:Integer - Rational{T}(convert(T, x), one(T); safe=false) + Rational{T}(convert(T, x), one(T); checked=false) end Rational(x::Rational) = x @@ -154,7 +146,7 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) -big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)); safe=false) +big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)); checked=false) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) @@ -190,7 +182,7 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer throw(OverflowError("cannot negate unsigned number")) end isnan(x) && return T(x)//one(T) - isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T); safe=false) + isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T); checked=false) p, q = (x < 0 ? -one(T) : one(T)), zero(T) pp, qq = zero(T), one(T) @@ -283,23 +275,23 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den; safe=false) -copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den; safe=false) +copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den; checked=false) +copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den; checked=false) abs(x::Rational) = Rational(abs(x.num), x.den) -typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T); safe=false) -typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T); safe=false) -typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T); safe=false) +typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T); checked=false) +typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T); checked=false) +typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T); checked=false) isinteger(x::Rational) = x.den == 1 -+(x::Rational) = Rational(+x.num, x.den; safe=false) --(x::Rational) = Rational(-x.num, x.den; safe=false) ++(x::Rational) = Rational(+x.num, x.den; checked=false) +-(x::Rational) = Rational(-x.num, x.den; checked=false) function -(x::Rational{T}) where T<:BitSigned x.num == typemin(T) && throw(OverflowError("rational numerator is typemin(T)")) - Rational(-x.num, x.den; safe=false) + Rational(-x.num, x.den; checked=false) end function -(x::Rational{T}) where T<:Unsigned x.num != zero(T) && throw(OverflowError("cannot negate unsigned number")) @@ -314,14 +306,14 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), (:rem,:rem), (:mod,:mod) end function ($op)(x::Rational, y::Integer) - Rational(($chop)(x.num, checked_mul(x.den, y)), x.den; safe=false) + Rational(($chop)(x.num, checked_mul(x.den, y)), x.den; checked=false) end end end for (op,chop) in ((:+,:checked_add), (:-,:checked_sub)) @eval begin function ($op)(y::Integer, x::Rational) - Rational(($chop)(checked_mul(x.den, y), x.num), x.den; safe=false) + Rational(($chop)(checked_mul(x.den, y), x.num), x.den; checked=false) end end end @@ -336,27 +328,21 @@ end function *(x::Rational, y::Rational) xn, yd = divgcd(x.num, y.den) xd, yn = divgcd(x.den, y.num) - Rational(checked_mul(xn, yn), checked_mul(xd, yd); safe=false) + Rational(checked_mul(xn, yn), checked_mul(xd, yd); checked=false) end function *(x::Rational, y::Integer) xd, yn = divgcd(x.den, y) - Rational(checked_mul(x.num, yn), xd; safe=false) + Rational(checked_mul(x.num, yn), xd; checked=false) end function *(y::Integer, x::Rational) yn, xd = divgcd(y, x.den) - Rational(checked_mul(yn, x.num), xd; safe=false) + Rational(checked_mul(yn, x.num), xd; checked=false) end /(x::Rational, y::Union{Rational, Integer}) = x//y /(x::Integer, y::Rational) = x//y /(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y function inv(x::Rational{T}) where T - num, den = x.den, x.num - if signbit(den) - den = -den - signbit(den) && __throw_rational_argerror_typemin(T) - num = -num - end - Rational(num, den; safe=false) + checked_den(x.den, x.num) end fma(x::Rational, y::Rational, z::Rational) = x*y+z @@ -474,7 +460,7 @@ round(x::Rational, r::RoundingMode=RoundNearest) = round(typeof(x), x, r) function round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T,Tr} if iszero(denominator(x)) && !(T <: Integer) - return convert(T, copysign(Rational(one(Tr), zero(Tr); safe=false), numerator(x))) + return convert(T, copysign(Rational(one(Tr), zero(Tr); checked=false), numerator(x))) end convert(T, div(numerator(x), denominator(x), r)) end @@ -508,8 +494,8 @@ end float(::Type{Rational{T}}) where {T<:Integer} = float(T) -gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den); safe=false) -lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den); safe=false) +gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den); checked=false) +lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den); checked=false) function gcdx(x::Rational, y::Rational) c = gcd(x, y) if iszero(c.num) From 7a92120a9123f3ab09794a4961cdcde463c05349 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Fri, 24 Apr 2020 09:55:01 +0200 Subject: [PATCH 06/10] Minor refactor _checked_den -> checking_den and make the keyword argument mandatory for the inner constructor --- base/rational.jl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index 5e008b2dcc3ac..2669e4ea1376c 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -10,11 +10,11 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer; checked::Bool=true) where T<:Integer + function Rational{T}(num::Integer, den::Integer; checked::Bool) where T<:Integer if checked num == den == zero(T) && __throw_rational_argerror_zero(T) num, den = divgcd(num, den) - num, den = _checked_den(num, den) + num, den = checking_den(num, den) end return new{T}(num, den) end @@ -22,7 +22,7 @@ end @noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) @noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -function _checked_den(num::T, den::T) where T<:Integer +function checking_den(num::T, den::T) where T<:Integer if signbit(den) den = -den signbit(den) && __throw_rational_argerror_typemin(T) @@ -32,10 +32,14 @@ function _checked_den(num::T, den::T) where T<:Integer end function checked_den(num::T, den::T) where T<:Integer - Rational{T}(_checked_den(num, den)...; checked=false) + Rational{T}(checking_den(num, den)...; checked=false) end checked_den(num::Integer, den::Integer) = checked_den(promote(num, den)...) +function Rational{T}(num::Integer, den::Integer) where T<:Integer + Rational{T}(num, den; checked=true) +end + function Rational(n::T, d::T; checked=true) where T<:Integer Rational{T}(n, d; checked) end From 3fd3acc637a95f5ba0c9d62a1c6b652f63ce21d3 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Fri, 24 Apr 2020 18:34:21 +0200 Subject: [PATCH 07/10] Fixes for @KlausC's review --- base/rational.jl | 4 ++-- test/rational.jl | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index 2669e4ea1376c..6a32b82789e20 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -12,9 +12,9 @@ struct Rational{T<:Integer} <: Real function Rational{T}(num::Integer, den::Integer; checked::Bool) where T<:Integer if checked - num == den == zero(T) && __throw_rational_argerror_zero(T) + iszero(den) && iszero(num) && __throw_rational_argerror_zero(T) num, den = divgcd(num, den) - num, den = checking_den(num, den) + num, den = checking_den(T(num), T(den)) end return new{T}(num, den) end diff --git a/test/rational.jl b/test/rational.jl index bb446840e7d29..4b2d48305660b 100644 --- a/test/rational.jl +++ b/test/rational.jl @@ -29,6 +29,7 @@ using Test @test_throws OverflowError (1//2)^63 @test inv((1+typemin(Int))//typemax(Int)) == -1 @test_throws ArgumentError inv(typemin(Int)//typemax(Int)) + @test_throws ArgumentError Rational(0x1, typemin(Int32)) @test @inferred(rationalize(Int, 3.0, 0.0)) === 3//1 @test @inferred(rationalize(Int, 3.0, 0)) === 3//1 @@ -42,6 +43,11 @@ using Test @test -2 // typemin(Int) == -1 // (typemin(Int) >> 1) @test 2 // typemin(Int) == 1 // (typemin(Int) >> 1) + @test_throws InexactError Rational(UInt(1), typemin(Int32)) + @test iszero(Rational{Int}(UInt(0), 1)) + @test Rational{BigInt}(UInt(1), Int(-1)) == -1 + @test_broken Rational{Int64}(UInt(1), typemin(Int32)) == Int64(1) // Int64(typemin(Int32)) + for a = -5:5, b = -5:5 if a == b == 0; continue; end if ispow2(b) From 75504c1bb5bb478aea94fc024fd7b00e0c295838 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Tue, 28 Apr 2020 10:33:03 +0200 Subject: [PATCH 08/10] Put back default value for checked and minor refactors --- base/rational.jl | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index 6a32b82789e20..71e6482a57e72 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -10,7 +10,7 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer; checked::Bool) where T<:Integer + function Rational{T}(num::Integer, den::Integer; checked::Bool=true) where T<:Integer if checked iszero(den) && iszero(num) && __throw_rational_argerror_zero(T) num, den = divgcd(num, den) @@ -36,10 +36,6 @@ function checked_den(num::T, den::T) where T<:Integer end checked_den(num::Integer, den::Integer) = checked_den(promote(num, den)...) -function Rational{T}(num::Integer, den::Integer) where T<:Integer - Rational{T}(num, den; checked=true) -end - function Rational(n::T, d::T; checked=true) where T<:Integer Rational{T}(n, d; checked) end @@ -160,6 +156,8 @@ promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer,S<:AbstractFloat} widen(::Type{Rational{T}}) where {T} = Rational{widen(T)} +@noinline __throw_negate_unsigned() = throw(OverflowError("cannot negate unsigned number")) + """ rationalize([T<:Integer=Int,] x; tol::Real=eps(x)) @@ -182,9 +180,7 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer if tol < 0 throw(ArgumentError("negative tolerance $tol")) end - if T<:Unsigned && x < 0 - throw(OverflowError("cannot negate unsigned number")) - end + T<:Unsigned && x < 0 && __throw_negate_unsigned() isnan(x) && return T(x)//one(T) isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T); checked=false) @@ -294,11 +290,13 @@ isinteger(x::Rational) = x.den == 1 -(x::Rational) = Rational(-x.num, x.den; checked=false) function -(x::Rational{T}) where T<:BitSigned - x.num == typemin(T) && throw(OverflowError("rational numerator is typemin(T)")) + x.num == typemin(T) && __throw_rational_numerator_typemin(T) Rational(-x.num, x.den; checked=false) end +@noinline __throw_rational_numerator_typemin(T) = throw(OverflowError("rational numerator is typemin($T)")) + function -(x::Rational{T}) where T<:Unsigned - x.num != zero(T) && throw(OverflowError("cannot negate unsigned number")) + x.num != zero(T) && __throw_negate_unsigned() x end @@ -342,9 +340,8 @@ function *(y::Integer, x::Rational) yn, xd = divgcd(y, x.den) Rational(checked_mul(yn, x.num), xd; checked=false) end -/(x::Rational, y::Union{Rational, Integer}) = x//y -/(x::Integer, y::Rational) = x//y -/(x::Rational, y::Complex{<:Union{Integer,Rational}}) = x//y +/(x::Rational, y::Union{Rational, Integer, Complex{<:Union{Integer,Rational}}}) = x//y +/(x::Union{Integer, Complex{<:Union{Integer,Rational}}}, y::Rational) = x//y function inv(x::Rational{T}) where T checked_den(x.den, x.num) end From 3f6ffa0c28e97c9ef3b61860489ac92a5a89ff47 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Fri, 1 May 2020 18:02:46 +0200 Subject: [PATCH 09/10] Refactor using unsafe_rational to avoid a new export --- base/rational.jl | 95 ++++++++++++++++++------------------------------ 1 file changed, 36 insertions(+), 59 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index 71e6482a57e72..e85b0fc079c1f 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -10,57 +10,34 @@ struct Rational{T<:Integer} <: Real num::T den::T - function Rational{T}(num::Integer, den::Integer; checked::Bool=true) where T<:Integer - if checked - iszero(den) && iszero(num) && __throw_rational_argerror_zero(T) - num, den = divgcd(num, den) - num, den = checking_den(T(num), T(den)) - end - return new{T}(num, den) - end + # Unexported inner constructor of Rational that bypasses all checks + global unsafe_rational(::Type{T}, num, den) where {T} = new{T}(num, den) end -@noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) -@noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) -function checking_den(num::T, den::T) where T<:Integer +unsafe_rational(num::T, den::T) where {T<:Integer} = unsafe_rational(T, num, den) +unsafe_rational(num::Integer, den::Integer) = unsafe_rational(promote(num, den)...) + +@noinline __throw_rational_argerror_typemin(T) = throw(ArgumentError("invalid rational: denominator can't be typemin($T)")) +function checked_den(num::T, den::T) where T<:Integer if signbit(den) den = -den signbit(den) && __throw_rational_argerror_typemin(T) num = -num end - return num, den -end - -function checked_den(num::T, den::T) where T<:Integer - Rational{T}(checking_den(num, den)...; checked=false) + return unsafe_rational(T, num, den) end checked_den(num::Integer, den::Integer) = checked_den(promote(num, den)...) -function Rational(n::T, d::T; checked=true) where T<:Integer - Rational{T}(n, d; checked) +@noinline __throw_rational_argerror_zero(T) = throw(ArgumentError("invalid rational: zero($T)//zero($T)")) +function Rational{T}(num::Integer, den::Integer) where T<:Integer + iszero(den) && iszero(num) && __throw_rational_argerror_zero(T) + num, den = divgcd(num, den) + return checked_den(T(num), T(den)) end -""" - Rational(num::Integer, den::Integer; checked::Bool=true) - Rational{T<:Integer}(num::Integer, den::Integer; checked::Bool=true) - -Create a `Rational` with numerator `num` and denominator `den`. - -A `Rational` is well-formed if its numerator and denominator are coprime and if the -denominator is non-negative. By default, the constructor checks its arguments so that -the created `Rational` is well-formed. Well-formedness is kept across operations on -`Rational`s. - -Unset the optional argument `checked` to bypass all checks. - -!!! warning - Using an ill-formed `Rational` result in undefined behavior. Only bypass checks if - `num` and `den` are known to satisfy them. -""" -function Rational(n::Integer, d::Integer; checked=true) - Rational(promote(n, d)...; checked) -end -Rational(n::Integer) = Rational(n, one(n); checked=false) +Rational(n::T, d::T) where {T<:Integer} = Rational{T}(n, d) +Rational(n::Integer, d::Integer) = Rational(promote(n, d)...) +Rational(n::Integer) = unsafe_rational(n, one(n)) function divgcd(x::Integer,y::Integer) g = gcd(x,y) @@ -119,10 +96,10 @@ function write(s::IO, z::Rational) end function Rational{T}(x::Rational) where T<:Integer - Rational{T}(convert(T, x.num), convert(T,x.den); checked=false) + unsafe_rational(T, convert(T, x.num), convert(T, x.den)) end function Rational{T}(x::Integer) where T<:Integer - Rational{T}(convert(T, x), one(T); checked=false) + unsafe_rational(T, convert(T, x), one(T)) end Rational(x::Rational) = x @@ -146,7 +123,7 @@ end Rational(x::Float64) = Rational{Int64}(x) Rational(x::Float32) = Rational{Int}(x) -big(q::Rational) = Rational(big(numerator(q)), big(denominator(q)); checked=false) +big(q::Rational) = unsafe_rational(big(numerator(q)), big(denominator(q))) big(z::Complex{<:Rational{<:Integer}}) = Complex{Rational{BigInt}}(z) @@ -182,7 +159,7 @@ function rationalize(::Type{T}, x::AbstractFloat, tol::Real) where T<:Integer end T<:Unsigned && x < 0 && __throw_negate_unsigned() isnan(x) && return T(x)//one(T) - isinf(x) && return Rational(x < 0 ? -one(T) : one(T), zero(T); checked=false) + isinf(x) && return unsafe_rational(x < 0 ? -one(T) : one(T), zero(T)) p, q = (x < 0 ? -one(T) : one(T)), zero(T) pp, qq = zero(T), one(T) @@ -275,23 +252,23 @@ denominator(x::Rational) = x.den sign(x::Rational) = oftype(x, sign(x.num)) signbit(x::Rational) = signbit(x.num) -copysign(x::Rational, y::Real) = Rational(copysign(x.num, y), x.den; checked=false) -copysign(x::Rational, y::Rational) = Rational(copysign(x.num, y.num), x.den; checked=false) +copysign(x::Rational, y::Real) = unsafe_rational(copysign(x.num, y), x.den) +copysign(x::Rational, y::Rational) = unsafe_rational(copysign(x.num, y.num), x.den) abs(x::Rational) = Rational(abs(x.num), x.den) -typemin(::Type{Rational{T}}) where {T<:Signed} = Rational(-one(T), zero(T); checked=false) -typemin(::Type{Rational{T}}) where {T<:Integer} = Rational(zero(T), one(T); checked=false) -typemax(::Type{Rational{T}}) where {T<:Integer} = Rational(one(T), zero(T); checked=false) +typemin(::Type{Rational{T}}) where {T<:Signed} = unsafe_rational(T, -one(T), zero(T)) +typemin(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(T, zero(T), one(T)) +typemax(::Type{Rational{T}}) where {T<:Integer} = unsafe_rational(T, one(T), zero(T)) isinteger(x::Rational) = x.den == 1 -+(x::Rational) = Rational(+x.num, x.den; checked=false) --(x::Rational) = Rational(-x.num, x.den; checked=false) ++(x::Rational) = unsafe_rational(+x.num, x.den) +-(x::Rational) = unsafe_rational(-x.num, x.den) function -(x::Rational{T}) where T<:BitSigned x.num == typemin(T) && __throw_rational_numerator_typemin(T) - Rational(-x.num, x.den; checked=false) + unsafe_rational(-x.num, x.den) end @noinline __throw_rational_numerator_typemin(T) = throw(OverflowError("rational numerator is typemin($T)")) @@ -308,14 +285,14 @@ for (op,chop) in ((:+,:checked_add), (:-,:checked_sub), (:rem,:rem), (:mod,:mod) end function ($op)(x::Rational, y::Integer) - Rational(($chop)(x.num, checked_mul(x.den, y)), x.den; checked=false) + unsafe_rational(($chop)(x.num, checked_mul(x.den, y)), x.den) end end end for (op,chop) in ((:+,:checked_add), (:-,:checked_sub)) @eval begin function ($op)(y::Integer, x::Rational) - Rational(($chop)(checked_mul(x.den, y), x.num), x.den; checked=false) + unsafe_rational(($chop)(checked_mul(x.den, y), x.num), x.den) end end end @@ -330,15 +307,15 @@ end function *(x::Rational, y::Rational) xn, yd = divgcd(x.num, y.den) xd, yn = divgcd(x.den, y.num) - Rational(checked_mul(xn, yn), checked_mul(xd, yd); checked=false) + unsafe_rational(checked_mul(xn, yn), checked_mul(xd, yd)) end function *(x::Rational, y::Integer) xd, yn = divgcd(x.den, y) - Rational(checked_mul(x.num, yn), xd; checked=false) + unsafe_rational(checked_mul(x.num, yn), xd) end function *(y::Integer, x::Rational) yn, xd = divgcd(y, x.den) - Rational(checked_mul(yn, x.num), xd; checked=false) + unsafe_rational(checked_mul(yn, x.num), xd) end /(x::Rational, y::Union{Rational, Integer, Complex{<:Union{Integer,Rational}}}) = x//y /(x::Union{Integer, Complex{<:Union{Integer,Rational}}}, y::Rational) = x//y @@ -461,7 +438,7 @@ round(x::Rational, r::RoundingMode=RoundNearest) = round(typeof(x), x, r) function round(::Type{T}, x::Rational{Tr}, r::RoundingMode=RoundNearest) where {T,Tr} if iszero(denominator(x)) && !(T <: Integer) - return convert(T, copysign(Rational(one(Tr), zero(Tr); checked=false), numerator(x))) + return convert(T, copysign(unsafe_rational(one(Tr), zero(Tr)), numerator(x))) end convert(T, div(numerator(x), denominator(x), r)) end @@ -495,8 +472,8 @@ end float(::Type{Rational{T}}) where {T<:Integer} = float(T) -gcd(x::Rational, y::Rational) = Rational(gcd(x.num, y.num), lcm(x.den, y.den); checked=false) -lcm(x::Rational, y::Rational) = Rational(lcm(x.num, y.num), gcd(x.den, y.den); checked=false) +gcd(x::Rational, y::Rational) = unsafe_rational(gcd(x.num, y.num), lcm(x.den, y.den)) +lcm(x::Rational, y::Rational) = unsafe_rational(lcm(x.num, y.num), gcd(x.den, y.den)) function gcdx(x::Rational, y::Rational) c = gcd(x, y) if iszero(c.num) From 40dc56df9a6154de0eb6a6eb8b3546c8631927ba Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Fri, 1 May 2020 22:32:14 +0200 Subject: [PATCH 10/10] minor: short form function for inv Co-authored-by: Mustafa M --- base/rational.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/base/rational.jl b/base/rational.jl index e85b0fc079c1f..05b4a062d8711 100644 --- a/base/rational.jl +++ b/base/rational.jl @@ -319,9 +319,7 @@ function *(y::Integer, x::Rational) end /(x::Rational, y::Union{Rational, Integer, Complex{<:Union{Integer,Rational}}}) = x//y /(x::Union{Integer, Complex{<:Union{Integer,Rational}}}, y::Rational) = x//y -function inv(x::Rational{T}) where T - checked_den(x.den, x.num) -end +inv(x::Rational{T}) where {T} = checked_den(x.den, x.num) fma(x::Rational, y::Rational, z::Rational) = x*y+z