diff --git a/Project.toml b/Project.toml index b3f5fab..b7bd2f3 100644 --- a/Project.toml +++ b/Project.toml @@ -4,11 +4,14 @@ name = "AngleBetweenVectors" author = "Jeffrey Sarnoff " uuid = "ec570357-d46e-52ed-9726-18773498274d" repo = "https://github.com/JeffreySarnoff/AngleBetweenVectors.jl.git" -version = "v0.3.0" +version = "v0.4.0" [deps] LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +[compat] +julia = "1" + [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/AngleBetweenVectors.jl b/src/AngleBetweenVectors.jl index 2415a20..f036d60 100644 --- a/src/AngleBetweenVectors.jl +++ b/src/AngleBetweenVectors.jl @@ -4,6 +4,7 @@ import Base: angle import LinearAlgebra: norm +Floats = Union{AbstractFloat, Complex{T} where T<:AbstractFloat} @inline unitize(p) = p ./ norm(p) @@ -20,11 +21,20 @@ Prefer this to `acos` alternatives Suggested when any |coordinate| of either point may be outside 2^±20 or [1/1_000_000, 1_000_000]. Strongly recommended when any |coordinate| is outside 2^±24 or [1/16_000_000, 16_000_000]. -If one of the points is at the origin, the result is zero. +If either of the points is at the origin, the result is `NaN` because angle is undefined in that case. You *must* define a tuple constructor `Tuple(x::YourPointType) = ...` if one does not already exist. """ -function angle(point1::A, point2::A) where {N,T<:Real,NT<:NTuple{N,T}, V<:Vector{T}, A<:Union{NT,V}} +angle(tuple1::NTuple{N,T}, tuple2::NTuple{N,T}) where {N, T<:Floats} = angle(real(T), tuple1, tuple2) + +# because of the broadcasts .- and .+ below, it is essential to check size compatibility +# for arrays, whereas for tuples the consistency ensured by the "N" in the type +function angle(a1::AbstractArray{T}, a2::AbstractArray{T}) where {T<:Floats} + size(a1) == size(a2) || throw(DimensionMismatch()) + return angle(real(T), a1, a2) +end + +function angle(::Type{T}, point1, point2) where {T<:AbstractFloat} unitpoint1 = unitize(point1) unitpoint2 = unitize(point2) @@ -36,6 +46,12 @@ function angle(point1::A, point2::A) where {N,T<:Real,NT<:NTuple{N,T}, V<:Vector !(signbit(a) || signbit(T(pi) - a)) ? a : (signbit(a) ? zero(T) : T(pi)) end +# this method allows the arrays to have different types, even non-float types +function angle(a1::AbstractArray{T1}, a2::AbstractArray{T2}) where {T1 <: Number, T2 <: Number} + T = float(promote_type(T1, T2)) # the "T" for T(pi) must be a float type + return angle(convert(AbstractArray{T}, a1), convert(AbstractArray{T}, a2)) +end + @inline angle(point1::T, point2::T) where {T} = angle(Tuple(point1), Tuple(point2)) end # AngleBetweenVectors diff --git a/test/runtests.jl b/test/runtests.jl index 07b811e..884ef89 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -18,3 +18,10 @@ point2 = (0.0, 1.0, 0.0, -1.0) # note: 2pi/3 !== Float64(2*BigFloat(pi)/3) @test angle(point1, point2) == Float64(2*BigFloat(pi)/3) @test angle(point1, point2) == angle(point2, point1) + +@test (@inferred angle(0:1, -1:0)) == pi/2 # abstract vector +@test (@inferred angle(0:1, -1.0:0.0)) == pi/2 # different eltype +@test (@inferred angle(Real[0.0 1], Int16[-1 0])) == pi/2 # array +@test (@inferred angle(0:1, (-1.0:0.0)*1im)) == pi/2 # complex + +@test isnan(@inferred angle(zeros(2), 1:2)) # zero point returns NaN