-
Notifications
You must be signed in to change notification settings - Fork 112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Preserve the floating-point precision of quantities in uconvert
#754
base: master
Are you sure you want to change the base?
Conversation
5608057
to
d1605dc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only drawback of this PR is that it now requires the numeric type of the quantity to implement the
real
function.
There are other places where real
is required, e.g. the calculation of the default atol
for isapprox
. But I’m not sure that this means we can require it here without breaking some code.
However, even if this breaks some code, it will produce an error, so it’s not that bad (it doesn’t silently return a wrong result) and someone affected by this might open an issue.
To be on the safe side, we could add a hasmethod
check (and turn the function into a @generated
function so that hasmethod
is only called once at compile time), like the suggested change below. But I’m not sure it is necessary. Do you know a number type that doesn’t implement real
? I don’t.
A Number type that is defined by another package, maybe. This problem only occurs because the We can assume that the However, the solution you recommended seems good. |
This is unfortunately not the case for the types from CliffordNumbers.jl (which supports Unitful numbers via a package extension). They implement julia> k = KVector{1, VGA(3)}(1, 2, 3)
3-element KVector{1, VGA(3), Int64}:
1e₁ + 2e₂ + 3e₃
julia> real(k)
3-element KVector{1, VGA(3), Int64}:
1e₁ + 2e₂ + 3e₃
julia> real(k) isa Real
false
julia> float(k)
3-element KVector{1, VGA(3), Float64}:
1.0e₁ + 2.0e₂ + 3.0e₃
julia> float(k) isa AbstractFloat
false This means that CliffordNumbers would not benefit from this change (a To make this non-widening behavior easy to opt-in, we could add a new function that packages can overload for their types. The fallback would be this: function floattype(::Type{T}) where T
try # Use try-catch instead of hasmethod because real(T) fallback method exists but might throw an error
float(real(T))
catch
Nothing
end
end Packages like CliffordNumbers.jl can then add their own What do you think? |
@sostock the floattype(::Type{T}) where {T<:AbstractFloat} = T
floattype(::Type{T}) where {T<:Complex} = floattype(real(T))
floattype(::Type{T}) where {T<:Number} = Float64 # or nothing, but Float64 seems like a good default What do you think? |
That would mean |
@sostock you're absolutely right. I think the most viable solution is actually the function floattype(::Type{T}) where {T<:Number}
try
F = float(real(T))
F isa AbstractFloat ? F : Float64 # or nothing
catch
Float64 # or nothing
end
end Since this function will run at compile time, I think the cost of the |
Famous last words! I would at least benchmark and compare
Also, is this package tested at all against the autodiff packages? I don't know how fragile those are, but |
I have already benchmarked some cases and will benchmark some more when we have decided which version to implement. I have actually found a slight performance regression (that is fortunately easy to fix), in the original version of the PR (the one without the julia> using BenchmarkTools,
julia> @btime uconvert(u"rad", $(big(45.0)u"°"));
109.262 ns (2 allocations: 104 bytes) # v1.21.1: conversion factor is a Float64
122.539 ns (4 allocations: 208 bytes) # this PR: conversion factor is converted to BigFloat It is unfortunate that the conversion factor in this case only has Regarding autodiff packages, I have to admit that I don’t have any experience with any of them, and they are not tested as part of our test suite. Since However, I also thought about another way of implementing this that shouldn’t require other packages to implement |
Great idea, @sostock. I think I can implement this idea in this same PR. |
Yes, I would only use it for float conversion factors. The main (or only) purpose of the |
src/conversion.jl
Outdated
struct UnitConversionFactor{x} <: AbstractIrrational end | ||
|
||
Base.:(==)(::UnitConversionFactor{a}, ::UnitConversionFactor{b}) where {a, b} = a == b | ||
Base.hash(x::UnitConversionFactor, h::UInt) = 3 * objectid(x) - h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Base.hash(x::UnitConversionFactor, h::UInt) = 3 * objectid(x) - h | |
Base.hash(::UnitConversionFactor{x}, h::UInt) where {x} = hash(x, h) |
Since UnitConversionFactor
is (for now) always exactly equal to a Float64
, it should hash the same. We should also implement ==(::UnitConversionFactor, ::Real)
and ==(::Real, ::UnitConversionFactor)
. However, since this type is only used internally, it probably doesn’t matter too much.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sostock adjusted. Do you think the ==
with Real
is necessary? If so, I can add the methods and tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably isn’t necessary, since ==
or hash
is likely never used on this type. But I still think hash
and isequal
(which falls back to ==
) should be consistent. So, I would either change back hash
to the old implementation (where the hashes are never equal to those of a Float64
) or add the ==
methods.
I think we could actually remove the hash
and ==
methods and they would still be consistent using the fallback methods.
uconvert
uconvert
uconvert
uconvert
uconvert
@sostock ready for a new review. |
master:
PR:
The only drawback of this PR is that it now requires the numeric type of the quantity to implement thereal
function.closes #753