From 268cee037f8d898ea838a1c0737d1f9a5e4f00a6 Mon Sep 17 00:00:00 2001 From: Seth Axen Date: Thu, 15 Dec 2022 12:10:56 +0100 Subject: [PATCH] Implement more base methods (#121) * Add methods implemented in complex.jl * Add tests * Add round to docs * Increment patch version number * Avoid line wrapping --- Project.toml | 2 +- docs/src/api.md | 4 ++++ src/Quaternion.jl | 57 ++++++++++++++++++++++++++++++++++++++++++++++ test/Quaternion.jl | 57 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e1413807..377064ab 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Quaternions" uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0" -version = "0.7.1" +version = "0.7.2" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/docs/src/api.md b/docs/src/api.md index 6251f38f..59935e5b 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -18,6 +18,10 @@ real(::Type{Quaternion{T}}) where {T} imag_part ``` +```@docs +round(::Quaternion) +``` + ```@docs conj ``` diff --git a/src/Quaternion.jl b/src/Quaternion.jl index 95200fd7..c3844ae6 100644 --- a/src/Quaternion.jl +++ b/src/Quaternion.jl @@ -147,6 +147,7 @@ Base.isfinite(q::Quaternion) = isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & Base.iszero(q::Quaternion) = iszero(real(q)) & iszero(q.v1) & iszero(q.v2) & iszero(q.v3) Base.isnan(q::Quaternion) = isnan(real(q)) | isnan(q.v1) | isnan(q.v2) | isnan(q.v3) Base.isinf(q::Quaternion) = isinf(q.s) | isinf(q.v1) | isinf(q.v2) | isinf(q.v3) +Base.isinteger(q::Quaternion) = isinteger(real(q)) & isreal(q) # included strictly for documentation; the base implementation is sufficient """ @@ -384,3 +385,59 @@ LinearAlgebra.lyap(a::Quaternion, c::Quaternion) = lyap(promote(a, c)...) # if a commutes with c, use a simpler expression LinearAlgebra.lyap(a::Real, c::Quaternion) = c / -2a LinearAlgebra.lyap(a::Quaternion, c::Real) = c / -2real(a) + +Base.widen(::Type{Quaternion{T}}) where {T} = Quaternion{widen(T)} + +Base.flipsign(x::Quaternion, y::Real) = ifelse(signbit(y), -x, x) + +function Base.read(io::IO, ::Type{Quaternion{T}}) where T<:Real + return Quaternion{T}(ntuple(_ -> read(io, T), Val(4))...) +end +Base.write(io::IO, q::Quaternion) = write(io, real(q), imag_part(q)...) + +Base.big(::Type{Quaternion{T}}) where {T<:Real} = Quaternion{big(T)} +Base.big(z::Quaternion{T}) where {T<:Real} = Quaternion{big(T)}(z) + +""" + round(q::Quaternion[, RoundingModeReal, [RoundingModeImaginary]]; kwargs...) + round(q::Quaternion, RoundingModeReal, + RoundingModeImaginary1, RoundingModeImaginary2, RoundingModeImaginary3; kwargs...) + +Return the nearest integral value of the same type as the quaternion-valued `q` to `q`, +breaking ties using the specified `RoundingMode`s. + +The first `RoundingMode` is used for rounding the real part while the second is used +for rounding the imaginary parts. Alternatively, a `RoundingMode` may be provided for each +part. + +The `kwargs` are the same as those for `round(::Real[, RoundingMode]; kwargs...)`. + +# Example +```jldoctest +julia> round(quat(3.14, 4.5, 8.3, -2.8)) +QuaternionF64(3.0, 4.0, 8.0, -3.0) +``` +""" +function Base.round( + q::Quaternion, + rs::RoundingMode=RoundNearest, + rv::RoundingMode=rs; + kwargs..., +) + return round(q, rs, rv, rv, rv; kwargs...) +end +function Base.round( + q::Quaternion, + rs::RoundingMode, + rv1::RoundingMode, + rv2::RoundingMode, + rv3::RoundingMode; + kwargs..., +) + return Quaternion( + round(real(q), rs; kwargs...), + round(q.v1, rv1; kwargs...), + round(q.v2, rv2; kwargs...), + round(q.v3, rv3; kwargs...), + ) +end diff --git a/test/Quaternion.jl b/test/Quaternion.jl index cfd32cae..978e3a82 100644 --- a/test/Quaternion.jl +++ b/test/Quaternion.jl @@ -227,6 +227,14 @@ end @test isnan(Quaternion(1, 2, 3, NaN)) end + @testset "isinteger" begin + @test isinteger(quat(3)) + @test isinteger(quat(4.0)) + @test !isinteger(quat(4.1)) + @test !isinteger(quat(3, 1, 2, 3)) + @test !isinteger(quat(4, 0, 1, 0)) + end + @testset "*" begin # verify basic correctness q1 = Quaternion(1,0,0,0) @@ -571,4 +579,53 @@ end @test_throws DivideError lyap(null, null) end end + + @testset "widen" begin + @test widen(Quaternion{Int}) === Quaternion{Int128} + @test widen(QuaternionF32) === QuaternionF64 + @test widen(QuaternionF64) === Quaternion{BigFloat} + @test widen(quat(1, 2, 3, 4)) === Quaternion{Int128}(1, 2, 3, 4) + q = rand(QuaternionF32) + @test widen(q) == convert(QuaternionF64, q) + q = rand(QuaternionF64) + @test widen(q) == convert(Quaternion{BigFloat}, q) + end + + @testset "flipsign" begin + q = rand(QuaternionF64) + @test flipsign(q, 2) == q + @test flipsign(q, -3) == -q + end + + @testset "read/write" begin + @testset "$T" for T in (Int16, Float32, Float64) + io = IOBuffer(; read=true, write=true) + q = rand(Quaternion{T}) + write(io, q) + seek(io, 0) + q2 = read(io, Quaternion{T}) + @test q == q2 + end + end + + @testset "big" begin + @test big(Quaternion{Int}) === Quaternion{BigInt} + @test big(QuaternionF64) === Quaternion{BigFloat} + @test big(quat(1, 2, 3, 4)) == Quaternion{BigInt}(1, 2, 3, 4) + q = rand(QuaternionF64) + @test big(q) == convert(Quaternion{BigFloat}, q) + end + + @testset "round" begin + q = quat(1.1, 2.5, -3.5, 2.3) + @test round(q) == quat(1.0, 2.0, -4.0, 2.0) + @test round(q; digits=1) == q + @test round(q, RoundUp) == quat(2.0, 3.0, -3.0, 3.0) + @test round(q, RoundUp; digits=1) == q + @test round(q, RoundUp, RoundToZero) == quat(2.0, 2.0, -3.0, 2.0) + @test round(q, RoundUp, RoundToZero; digits=1) == q + rmodes = (RoundUp, RoundDown, RoundNearestTiesAway, RoundToZero) + @test round(q, rmodes...) == quat(2.0, 2.0, -4.0, 2.0) + @test round(q, rmodes...; digits=1) == q + end end