diff --git a/.gitignore b/.gitignore index 721a6b78..3e8794f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.vscode *.swp *.jl.cov *.jl.*.cov diff --git a/Project.toml b/Project.toml index 427a1a11..7ca2e139 100644 --- a/Project.toml +++ b/Project.toml @@ -17,6 +17,7 @@ Requires = "0.5.0, 1" julia = "1" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" QuadGK = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" @@ -24,4 +25,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [targets] -test = ["QuadGK", "SpecialFunctions", "Statistics", "Test", "Unitful"] +test = ["Aqua", "QuadGK", "SpecialFunctions", "Statistics", "Test", "Unitful"] diff --git a/README.md b/README.md index b7915cfe..75384b6a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Measurements.jl -| **Documentation** | **Build Status** | **Code Coverage** | -|:---------------------------------------:|:-----------------------------------:|:-------------------------------:| -| [![][docs-stable-img]][docs-stable-url] | [![Build Status][gha-img]][gha-url] | [![][coveral-img]][coveral-url] | +| **Documentation** | **Build Status** | **Code Coverage** | **Quality** | +|:---------------------------------------:|:-----------------------------------:|:-------------------------------:|:-----------:| +| [![][docs-stable-img]][docs-stable-url] | [![Build Status][gha-img]][gha-url] | [![][coveral-img]][coveral-url] |[![Aqua QA][aqua-img]](aqua-url)| | [![][docs-latest-img]][docs-latest-url] | | [![][codecov-img]][codecov-url] | Introduction @@ -219,3 +219,6 @@ is provided in the [`CITATION.bib`](CITATION.bib) file. [codecov-img]: https://codecov.io/gh/JuliaPhysics/Measurements.jl/branch/master/graph/badge.svg [codecov-url]: https://codecov.io/gh/JuliaPhysics/Measurements.jl + +[aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg +[aqua-url]: https://github.com/JuliaTesting/Aqua.jl \ No newline at end of file diff --git a/src/Measurements.jl b/src/Measurements.jl index 83e31ea6..2087fbf6 100644 --- a/src/Measurements.jl +++ b/src/Measurements.jl @@ -60,8 +60,18 @@ function Measurement(val::V, err::E, tag::UInt64, return Measurement(T(val), T(err), tag, Derivatives{T}(der)) end Measurement{T}(x::Measurement{S}) where {T,S} = convert(Measurement{T}, x) -Measurement{T}(x::S) where {T,S} = convert(Measurement{T}, x) -Measurement{T}(x::S) where {T,S<:Rational} = convert(Measurement{T}, x) +Measurement{T}(x::S) where {T,S <: Real} = convert(Measurement{T}, x) + +# disambiguities +Measurement{T}(x::S) where {T, S<:Rational} = convert(Measurement{T}, x) +Measurement{T}(x::S) where {T, S<:Complex} = convert(Measurement{T}, x) +Measurement{T}(x::S) where {T, S<:Base.TwicePrecision} = convert(Measurement{T}, x) +Measurement{T}(x::S) where {P, T, S<:Rational{P}} = convert(Measurement{T}, x) +Measurement{T}(x::S) where {T, S <: AbstractChar} = convert(Measurement{T}, x) + +function Measurement{T}(::S) where {T, S} + throw(ArgumentError("cannot convert `$S` to `Measurement{$T}`")) +end # Functions to quickly create an empty Derivatives object. @generated empty_der1(x::Measurement{T}) where {T<:AbstractFloat} = Derivatives{T}() diff --git a/src/comparisons-tests.jl b/src/comparisons-tests.jl index d673052b..eb7a04d8 100644 --- a/src/comparisons-tests.jl +++ b/src/comparisons-tests.jl @@ -25,9 +25,11 @@ Base.:(==)(a::Measurement, b::Measurement) = (a.val==b.val && a.err==b.err) Base.:(==)(a::Measurement, b::Irrational) = false Base.:(==)(a::Measurement, b::Rational) = (a.val==b && iszero(a.err)) Base.:(==)(a::Measurement, b::Real) = (a.val==b && iszero(a.err)) +Base.:(==)(a::Measurement, b::AbstractIrrational) = (a.val==b && iszero(b.err)) Base.:(==)(a::Irrational, b::Measurement) = false Base.:(==)(a::Rational, b::Measurement) = (a==b.val && iszero(b.err)) Base.:(==)(a::Real, b::Measurement) = (a==b.val && iszero(b.err)) +Base.:(==)(a::AbstractIrrational, b::Measurement) = (a==b.val && iszero(b.err)) # Create a hashing function that matches the same behaviour as `==`: only the # `val` and `err` fields matter. @@ -39,8 +41,10 @@ for cmp in (:<, :<=) Base.$cmp(a::Measurement, b::Measurement) = ($cmp)(a.val, b.val) Base.$cmp(a::Measurement, b::Rational) = ($cmp)(a.val, b) Base.$cmp(a::Measurement, b::Real) = ($cmp)(a.val, b) + Base.$cmp(a::Measurement, b::AbstractIrrational) = ($cmp)(a.val, b) Base.$cmp(a::Rational, b::Measurement) = ($cmp)(a, b.val) Base.$cmp(a::Real, b::Measurement) = ($cmp)(a, b.val) + Base.$cmp(a::AbstractIrrational, b::Measurement) = ($cmp)(a, b.val) end end diff --git a/src/conversions.jl b/src/conversions.jl index 499204d8..753ebfbe 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -22,6 +22,18 @@ Base.convert(::Type{Measurement{T}}, a::Rational{<:Integer}) where {T<:AbstractF measurement(T(a))::Measurement{T} Base.convert(::Type{Measurement{T}}, a::Real) where {T<:AbstractFloat} = measurement(T(a))::Measurement{T} +Base.convert(::Type{Measurement{T}}, a::Base.TwicePrecision) where {T<:AbstractFloat} = + measurement(T(a))::Measurement{T} +Base.convert(::Type{Measurement{T}}, a::AbstractChar) where {T<:AbstractFloat} = + measurement(T(a))::Measurement{T} + +function Base.convert(::Type{Measurement{T}}, a::Complex) where {T} + if isreal(a) + measurement(T(real(a))) + else + throw(InexactError(:convert, Measurement{T}, a)) + end +end Base.convert(::Type{Measurement{T}}, a::Measurement{T}) where {T<:AbstractFloat} = a function Base.convert(::Type{Measurement{T}}, diff --git a/src/math.jl b/src/math.jl index f6ca9c96..4bb1284b 100644 --- a/src/math.jl +++ b/src/math.jl @@ -56,7 +56,7 @@ end # Get the common type parameter of a collection of Measurement objects. The first two # methods are for the trivial cases of homogeneous tuples and arrays, the last, inefficient, # method is for inhomogeneous collections (probably the least common case). -gettype(::Tuple{Vararg{Measurement{T}}}) where {T<:AbstractFloat} = T +gettype(::Tuple{Measurement{T}, Vararg{Measurement{T}}}) where {T<:AbstractFloat} = T gettype(::AbstractArray{Measurement{T}}) where {T<:AbstractFloat} = T _eltype(::Measurement{T}) where {T<:AbstractFloat} = T gettype(collection) = promote_type(_eltype.(collection)...) @@ -174,6 +174,7 @@ end # Addition: + Base.:+(a::Measurement, b::Measurement) = result(a.val + b.val, (1, 1), (a, b)) Base.:+(a::Real, b::Measurement) = result(a + b.val, 1, b) +Base.:+(a::Bool, b::Measurement) = result(a + b.val, 1, b) Base.:+(a::Measurement, b::Bool) = result(a.val + b, 1, a) Base.:+(a::Measurement, b::Real) = result(a.val + b, 1, a) @@ -685,9 +686,12 @@ Base.sign(a::Measurement) = result(sign(a.val), 0, a) Base.copysign(a::Measurement, b::Measurement) = ifelse(signbit(a)!=signbit(b), -a, a) Base.copysign(a::Measurement, b::Real) = ifelse(signbit(a)!=signbit(b), -a, a) +Base.copysign(a::Measurement, b::Unsigned) = ifelse(signbit(a)!=signbit(b), -a, a) + Base.flipsign(a::Measurement, b::Measurement) = ifelse(signbit(b), -a, a) Base.flipsign(a::Measurement, b::Real) = ifelse(signbit(b), -a, a) -for T in (Signed, Rational, Float32, Float64, Real) +Base.flipsign(a::Measurement, b::Unsigned) = ifelse(signbit(b), -a, a) +for T in (Signed, Unsigned, Rational, Float32, Float64, Real) @eval Base.copysign(a::$T, b::Measurement) = copysign(a, b.val) @eval Base.flipsign(a::$T, b::Measurement) = flipsign(a, b.val) end @@ -734,6 +738,15 @@ Base.round(a::Measurement, r::RoundingMode=RoundNearest; kwargs...) = measurement(round(value(a), r; kwargs...), round(uncertainty(a); kwargs...)) Base.round(::Type{T}, a::Measurement, r::RoundingMode=RoundNearest) where {T<:Integer} = round(T, a.val, r) + +# disambiguities +Base.round(a::Measurement, r::RoundingMode{:NearestTiesAway}; kwargs...) = + measurement(round(value(a), r; kwargs...), round(uncertainty(a), r; kwargs...)) +Base.round(a::Measurement, r::RoundingMode{:NearestTiesUp}; kwargs...) = + measurement(round(value(a), r; kwargs...), round(uncertainty(a), r; kwargs...)) +Base.round(::Type{T}, a::Measurement, r::RoundingMode{:ToZero}) where {T<:Integer} = + measurement(round(T, value(a), r), round(uncertainty(a), r)) + Base.floor(a::Measurement) = measurement(floor(a.val)) Base.floor(::Type{T}, a::Measurement) where {T<:Integer} = floor(T, a.val) Base.ceil(a::Measurement) = measurement(ceil(a.val)) diff --git a/test/runtests.jl b/test/runtests.jl index 73449029..2f26993a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,6 @@ using Measurements, SpecialFunctions, QuadGK, Calculus -using Test, LinearAlgebra, Statistics, Unitful, Printf +using Test, LinearAlgebra, Statistics, Unitful, Printf, Aqua +Aqua.test_all(Measurements) import Base: isapprox import Measurements: value, uncertainty @@ -46,6 +47,14 @@ end @test x != pi != y end +@testset "constructors" begin + @test Measurement{Float64}(1.0 + 0im) ≈ 1.0 ± 0.0 + @test Measurement{Float64}(1//2) ≈ 0.5 ± 0.0 + @test Measurement{Float64}((1:1e-2:2).step) ≈ 1e-2±0.0 + @test Measurement{Float64}('a') ≈ Float64('a') ± 0.0 + @test_throws ArgumentError Measurement{Float64}("aaa") +end + @testset "missing values" begin @test measurement(missing) === missing @test measurement(missing, .1) === missing @@ -104,6 +113,11 @@ end Measurement{Float64} @test promote_type(Measurement{BigFloat}, Measurement{Float64}) == Measurement{BigFloat} + + @test convert(Measurement{Float64}, 1+0im) ≈ 1.0±0.0 + @test_throws InexactError convert(Measurement{Float64}, 1+1im) + @test convert(Measurement{Float64}, Base.TwicePrecision(1.0, 0.0)) ≈ 1.0±0.0 + @test convert(Measurement{Float64}, 'a') ≈ Float64('a') ± 0.0 end @testset "Comparisons and Tests" begin @@ -136,6 +150,8 @@ end @test isone(one(Measurement)) @test !isone(1 ± 1) @test !isone(0 ± 0) + @test 1//2 ± 0.0 == 1//2 + @test 1//2 == 1//2 ± 0.0 end @testset "Hashing and dictionaries" begin @@ -562,6 +578,9 @@ end @test @inferred(rem2pi(a, r)) ≈ rem(a, 2pi, r) @test rem2pi(a, r) ≈ a - 2pi * round(a / (2pi), r) end + + @test round(3.141234±0.1, RoundNearestTiesAway) ≈ 3.0±0.0 + @test round(3.141234±0.1, RoundNearestTiesUp) ≈ 3.0±0.0 end @testset "Machine precision" begin