diff --git a/src/conversion.jl b/src/conversion.jl index 8a32ac63..e244911a 100644 --- a/src/conversion.jl +++ b/src/conversion.jl @@ -1,3 +1,24 @@ +""" + UnitConversionFactor(x::AbstractFloat) +Conversion factor with value `x`. + +Used by the [`convfact`](@ref) function to preserve +the floating-point precision of quantities. +""" +struct UnitConversionFactor{T<:AbstractFloat} <: AbstractIrrational + x::T + # the inner constructor necessary for ambiguity resolution + UnitConversionFactor(x::T) where {T<:AbstractFloat} = new{T}(x) +end + +Base.:*(a::UnitConversionFactor, b::BigFloat) = a.x * b +Base.:*(a::BigFloat, b::UnitConversionFactor) = a * b.x +Base.:(==)(a::UnitConversionFactor, b::UnitConversionFactor) = a.x == b.x +Base.hash(x::UnitConversionFactor, h::UInt) = hash(x.x, h) +Base.BigFloat(x::UnitConversionFactor) = BigFloat(x.x) +Base.Float64(x::UnitConversionFactor) = Float64(x.x) +Base.Float32(x::UnitConversionFactor) = Float32(x.x) + """ convfact(s::Units, t::Units) Find the conversion factor from unit `t` to unit `s`, e.g., `convfact(m, cm) == 1//100`. @@ -37,7 +58,7 @@ Find the conversion factor from unit `t` to unit `s`, e.g., `convfact(m, cm) == "exponents and/or SI prefixes in units" )) end - return :($result) + return result isa AbstractFloat ? UnitConversionFactor(result) : result end """ diff --git a/test/runtests.jl b/test/runtests.jl index cfc39070..dd32a39d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -228,6 +228,24 @@ end # Issue 647: @test uconvert(u"kb^1000", 1u"kb^1001 * b^-1") === 1000u"kb^1000" @test uconvert(u"kOe^1000", 1u"kOe^1001 * Oe^-1") === 1000u"kOe^1000" + # Issue 753: + # preserve the floating-point precision of quantities + @test Unitful.numtype(uconvert(m, BigFloat(100)cm)) === BigFloat + @test Unitful.numtype(uconvert(cm, (BigFloat(1)π + im) * m)) === Complex{BigFloat} + @test Unitful.numtype(uconvert(rad, BigFloat(360)°)) === BigFloat + @test Unitful.numtype(uconvert(°, (BigFloat(2)π + im) * rad)) === Complex{BigFloat} + @test Unitful.numtype(uconvert(m, 100.0cm)) === Float64 + @test Unitful.numtype(uconvert(cm, (1.0π + im) * m)) === ComplexF64 + @test Unitful.numtype(uconvert(rad, 360.0°)) === Float64 + @test Unitful.numtype(uconvert(°, (2.0π + im) * rad)) === ComplexF64 + @test Unitful.numtype(uconvert(m, 100f0cm)) === Float32 + @test Unitful.numtype(uconvert(cm, (1f0π + im) * m)) === ComplexF32 + @test Unitful.numtype(uconvert(rad, 360f0°)) === Float32 + @test Unitful.numtype(uconvert(°, (2f0π + im) * rad)) === ComplexF32 + @test Unitful.numtype(uconvert(m, Float16(100)cm)) === Float16 + @test Unitful.numtype(uconvert(cm, (Float16(1)π + im) * m)) === ComplexF16 + @test Unitful.numtype(uconvert(rad, Float16(360)°)) === Float16 + @test Unitful.numtype(uconvert(°, (Float16(2)π + im) * rad)) === ComplexF16 # Floating point overflow/underflow in uconvert can happen if the # conversion factor is large, because uconvert does not cancel # common basefactors (or just for really large exponents and/or