-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
base/twiceprecision.jl needs work #49589
Comments
As the author of twiceprecision, I would love it if someone who knew this stuff fixed it. The only reason I worked on this at all was because I wanted to make ranges generic (#18777) and I couldn't do that while breaking a bunch of floating-point precision tests. So the current implementation is me thrashing my way towards not breaking any tests, but a more principled effort would be appreciated. I have no useful input to provide about the design, I'm sure you'll come up with something better. |
That'd be contradicting its docstring:
Also, regarding more words:
Does Base have the ambition to implement that, or should this be in a package? |
Safety is now improved in that it's not possible to construct nonsensical `TwicePrecision` types any more. Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates JuliaLang#49589
Not sure whether you're aware that |
Safety is now improved in that it's not possible to construct nonsensical `TwicePrecision` types any more. Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates JuliaLang#49589
Safety is now improved in that constructing nonsensical `TwicePrecision` types is now less likely. Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates JuliaLang#49589
Safety is now improved in that constructing nonsensical `TwicePrecision` types is now less likely. Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates JuliaLang#49589
I'm specifically referring to the The non- |
Adding to @Seelengrab's points, note that the limited functionality is largely intentional. Were this functionality to become public, one then must ask how you create ranges of |
Yes - |
Just disregard |
Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates JuliaLang#49589
Update the arithmetic code to use algorithms published since twiceprecision.jl got written. Handle complex numbers. Prevent harmful promotions. Some of the introduced extended precision arithmetic code will also be used in `base/div.jl` to tackle issue JuliaLang#49450 with PR JuliaLang#49561. TODO: evaluate the accuracy improvements and write tests after JuliaLang#49616 gets merged Results: The example in JuliaLang#33677 still gives the same result. I wasn't able to evaluate JuliaLang#23497 because of how much Julia changed in the meantime. TODO: check whether there are improvements for JuliaLang#26553 Updates JuliaLang#49589
Update the arithmetic code to use algorithms published since twiceprecision.jl got written. Handle complex numbers. Prevent harmful promotions. Some of the introduced extended precision arithmetic code will also be used in `base/div.jl` to tackle issue JuliaLang#49450 with PR JuliaLang#49561. TODO: evaluate the accuracy improvements and write tests after JuliaLang#49616 gets merged Results: The example in JuliaLang#33677 still gives the same result. I wasn't able to evaluate JuliaLang#23497 because of how much Julia changed in the meantime. TODO: check whether there are improvements for JuliaLang#26553 Updates JuliaLang#49589
Update the arithmetic code to use algorithms published since twiceprecision.jl got written. Handle complex numbers. Prevent harmful promotions. Some of the introduced extended precision arithmetic code will also be used in `base/div.jl` to tackle issue JuliaLang#49450 with PR JuliaLang#49561. TODO: evaluate the accuracy improvements and write tests after JuliaLang#49616 gets merged Results: The example in JuliaLang#33677 still gives the same result. I wasn't able to evaluate JuliaLang#23497 because of how much Julia changed in the meantime. TODO: check whether there are improvements for JuliaLang#26553 Updates JuliaLang#49589
Correctness is improved for constructing `TwicePrecision{T}` from `TwicePrecision{S}`. Previously a call like `TwicePrecision{Float64}(::TwicePrecision{Float32})` would leave `hi` and `lo` unnormalized; while a call like `TwicePrecision{Float32}(::TwicePrecision{Float64})` would additionally introduce unnecessary truncation. Accuracy is improved for constructing `TwicePrecision` from types like `BigFloat` or `Rational`. Previously a call like `TwicePrecision{Float64}(::BigFloat)` would unconditionally leave `lo` as zero. Example code that tests the improvements (all Boolean values should evaluate to `true`): ```julia const TwicePrecision = Base.TwicePrecision const canonicalize2 = Base.canonicalize2 function twiceprecision_roundtrip_is_not_lossy( ::Type{S}, x::T, ) where {S<:Number, T<:Union{Number,TwicePrecision}} tw = TwicePrecision{S}(x) x == T(tw) end function twiceprecision_is_normalized(tw::Tw) where {Tw<:TwicePrecision} (hi, lo) = (tw.hi, tw.lo) normalized = Tw(canonicalize2(hi, lo)...) (abs(lo) ≤ abs(hi)) & (tw == normalized) end rand_twiceprecision(::Type{T}) where {T<:Number} = TwicePrecision{T}(rand(widen(T))) rand_twiceprecision_is_ok(::Type{T}) where {T<:Number} = !iszero(rand_twiceprecision(T).lo) setprecision(BigFloat, 70) # The mantissa needs to be just a bit larger than for `Float64` rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float32) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) rand_twiceprecision_is_ok(Float64) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand(BigFloat)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_roundtrip_is_not_lossy(Float64, rand_twiceprecision(Float32)) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float32}(rand_twiceprecision(Float64))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) twiceprecision_is_normalized(TwicePrecision{Float64}(rand_twiceprecision(Float32))) ``` Updates #49589
Update the arithmetic code to use algorithms published since twiceprecision.jl got written. Handle complex numbers. Prevent some harmful promotions. Some of the introduced extended precision arithmetic code will also be used in `base/div.jl` to tackle issue JuliaLang#49450 with PR JuliaLang#49561. TODO: write tests TODO: some tests fail sometimes: ``` Test Failed at /home/nsajko/tmp/julia/test/ranges.jl:223 Expression: cmp_sn2(Tw(xw / yw), astuple(x / y)..., slopbits) ``` Results: The example in JuliaLang#33677 still gives the same result. I wasn't able to evaluate JuliaLang#23497 because of how much Julia changed in the meantime. Proof that JuliaLang#26553 is fixed: ```julia-repl julia> const TwicePrecision = Base.TwicePrecision Base.TwicePrecision julia> a = TwicePrecision{Float64}(0.42857142857142855, 2.3790493384824782e-17) Base.TwicePrecision{Float64}(0.42857142857142855, 2.3790493384824782e-17) julia> b = TwicePrecision{Float64}(3.4, 8.881784197001253e-17) Base.TwicePrecision{Float64}(3.4, 8.881784197001253e-17) julia> a_minus_b_inexact = Tuple(a - b) (-2.9714285714285715, 1.0150610510858574e-16) julia> BigFloat(a) == sum(map(BigFloat, Tuple(a))) true julia> BigFloat(b) == sum(map(BigFloat, Tuple(b))) true julia> a_minus_b = BigFloat(a) - BigFloat(b) -2.971428571428571428571428571428577679589762353999797347402693647078382455095635 julia> Float64(a_minus_b) == first(a_minus_b_inexact) true julia> Float64(a_minus_b - Float64(a_minus_b)) == a_minus_b_inexact[begin + 1] true ``` Fixes JuliaLang#26553 Updates JuliaLang#49589
Thanks for the enthusiasm! As noted above, Lines 168 to 173 in c90aa77
Many of the original design decisions were based on that (in particular, the original design didn't rely on FMAs, as the hardware operation wasn't available on all architectures, but that has since become less of a concern). It isn't really intended to be used as a generic number type (and so doesn't implement all the necessary operations one would want). For a fully-featured type, there is the DoubleFloats.jl package. The primary downside to making it a full-featured type in Base is that it is extremely difficult to upgrade user code: if you make improvements or fixes to DoubleFloats.jl, then any user can |
Update the arithmetic code to use algorithms published since twiceprecision.jl got written. Handle complex numbers. Prevent some harmful promotions. Some of the introduced extended precision arithmetic code will also be used in `base/div.jl` to tackle issue JuliaLang#49450 with PR JuliaLang#49561. Results: The example in JuliaLang#33677 still gives the same result. I wasn't able to evaluate JuliaLang#23497 because of how much Julia changed in the meantime. Proof that JuliaLang#26553 is fixed: ```julia-repl julia> const TwicePrecision = Base.TwicePrecision Base.TwicePrecision julia> a = TwicePrecision{Float64}(0.42857142857142855, 2.3790493384824782e-17) Base.TwicePrecision{Float64}(0.42857142857142855, 2.3790493384824782e-17) julia> b = TwicePrecision{Float64}(3.4, 8.881784197001253e-17) Base.TwicePrecision{Float64}(3.4, 8.881784197001253e-17) julia> a_minus_b_inexact = Tuple(a - b) (-2.9714285714285715, 1.0150610510858574e-16) julia> BigFloat(a) == sum(map(BigFloat, Tuple(a))) true julia> BigFloat(b) == sum(map(BigFloat, Tuple(b))) true julia> a_minus_b = BigFloat(a) - BigFloat(b) -2.971428571428571428571428571428577679589762353999797347402693647078382455095635 julia> Float64(a_minus_b) == first(a_minus_b_inexact) true julia> Float64(a_minus_b - Float64(a_minus_b)) == a_minus_b_inexact[begin + 1] true ``` Fixes JuliaLang#26553 Updates JuliaLang#49589
I never said I want |
I think a good way of going about that is to point to areas where the float range code have bugs and how improving |
Disables some constructors to help in catching errors earlier. Disables converting constructors like, for example, `TwicePrecision{Float64}(::BigFloat, ::BigFloat)`. This constructor was actually used in some cases before commit "Base: TwicePrecision: improve constructors (JuliaLang#49616)" (ce2c3ae), so disabling the constructor would have brought the possible accuracy losses and construction of unnormalized double-word numbers to attention sooner. Also effectively *blacklists* some types, to prevent them from being the type of a word in a `TwicePrecision` type. For example, `TwicePrecision{<:Integer}` objects are now disallowed, which is good as such a type would be nonsensical. Blacklisting is required, as opposed to whitelisting, because of the need to support various number-like types that are not `<:Number`. In the same way it is now forbidden to construct `TwicePrecision{T}` objects where `T` is not a concrete type. This should prevent the construction of `TwicePrecision` objects with the high and low words of differing types. Updates JuliaLang#49589
Disables some constructors to help in catching errors earlier. Disables converting constructors like, for example, `TwicePrecision{Float64}(::BigFloat, ::BigFloat)`. This constructor was actually used in some cases before commit "Base: TwicePrecision: improve constructors (JuliaLang#49616)" (ce2c3ae), so disabling the constructor would have brought the possible accuracy losses and construction of unnormalized double-word numbers to attention sooner. Also effectively *blacklists* some types, to prevent them from being the type of a word in a `TwicePrecision` type. For example, `TwicePrecision{<:Integer}` objects are now disallowed, which is good as such a type would be nonsensical. Blacklisting is required, as opposed to whitelisting via type constraints, because of the need to support various floating-point-like types that are not `<:AbstractFloat`, and, more generally, various number-like types that are not `<:Number` (such as types representing physical units). In the same way it is now forbidden to construct `TwicePrecision{T}` objects where `T` is not a concrete type. This should prevent the construction of `TwicePrecision` objects with the high and low words of differing types. Updates JuliaLang#49589
When I said
I was referring to this requirement stated in Line 16 in c90aa77
The requirement seems to be satisfied by ensuring that the least significant bits of the most significant word ( Line 136 in 9201414
The other requirements satisfied by Can someone who remembers please explain the rationale behind this requirement? Interestingly, the only place where Line 212 in c90aa77
It would, however, seem much more natural to just use the more general constructor immediately above, the effect would be the same except that |
I'd have to dig through the diffs, but the original motivation was for what is known as Veltkamp splitting, as a way to get higher-precision multiplication. Now that we have hardware fma ops on most platforms, I think most of the code has switched to using that (but I haven't looked for sure). |
Most code has switched to using fma, either directly or through llvm allowed muladd->fma or via MuladdMacro.jl afaik |
Algorithms for arithmetic (
+
,-
,*
,/
)The algorithms for real (as opposed to complex) double-word addition, multiplication and division were seemingly (there are no relevant explanatory comments or references in the code) all created ad-hoc and need to be updated to the standard algos, something I'll try to do in #49569.
Regarding the algorithms for complex numbers, they seem to be missing entirely, causing
Base.TwicePrecision{<:Complex}
to fall back to the algorithms for real numbers. This is OK for addition becauseComplex
is a Cartesian (orthogonal) representation of complex numbers, but multiplication and division could be quite broken. This could be fixed by adding methods forBase.TwicePrecision{<:Complex}
tobase/complex.jl
. A paper exists for double-word complex multiplication, and I guess I could work something out for division.Design of the
Base.TwicePrecision
typeThe
Base.TwicePrecision
type has an awkward and unsafe design. It accepts elements of any type, not just floating-point-based types. I propose this design instead as a replacement:In
base/twiceprecision.jl
:In
base/complex.jl
:Note that
MultiWordFloatingPointComplex
only makes sense becauseComplex
is an orthogonal coordinate representation of the complex numbers. A polar representation, for example, would be nonsensical in this context because a multi-word representation represents an unevaluated sum.The
n
type parameter would be restricted to two in practice for the time being, however I think that having it as a parameter right away gives a more elegant design.Is supporting complex numbers even necessary?
The point of
Base.TwicePrecision
is supporting ranges, and while ranges with complex elements do exist, I think the step is always real? This seems like it would mean that double-word algorithms for complex numbers are an unnecessary evil.EDIT: the answer seems to be yes, supporting complex numbers is necessary:
splitprec
This is more of a question than an issue, but I find this dubious so I thought it'd be good to raise it here with the other stuff.
Why does
TwicePrecision{<:AbstractFloat}(::Integer)
use thatsplitprec
function:https://github.com/JuliaLang/julia/blob/master/base/twiceprecision.jl#L213-L214
The fallback conversion method
TwicePrecision{<:Any}(::Any)
is sufficient for converting integers to double-word floating-point:https://github.com/JuliaLang/julia/blob/master/base/twiceprecision.jl#L203-L207
So it seems that the intention is to give up accuracy on purpose while converting integers to
TwicePrecision
? It's likely that I'm missing something, but I find this quite strange and I can't imagine why would one desire such behavior. Also, it seems that something similar is happening when thetruncbits
function is used.The text was updated successfully, but these errors were encountered: