julia
julia copied to clipboard
speed up Ryu.pow5
The current algorithm does up to p divisions, while this algorithm does only 1. I've assigned @quinnj to review since I'm not 100% sure that there isn't a very good reason that this function isn't written like this.
I'm not 100% sure that there isn't a very good reason that this function isn't written like this.
My guess would be overflow protection for sufficiently large p? Does wraparound prevent any shenanigans that could happen due to that?
Do you know if this needs an overflow check?
I don't off the top of my head
Maybe run a PkgEval and merge if it looks ok?
It feels kind of silly to use pkgeval for something this small, but I guess we might as well.
Or just merge, tests pass after all and I guess we will find it in release PkgEval if it is too bad
#yolo
Felt I should look at this since printing numbers incorrectly would be bad. This use of pow5 seems safe:
https://github.com/JuliaLang/julia/blob/f71b8398e6f6eca93eb6f7cefaa940aa7e2b1f5a/base/ryu/shortest.jl#L58-L66
My reasoning is that 5^p is correct for p ≤ 27 and here we have that q ≤ qinvbound(T) defined here:
https://github.com/JuliaLang/julia/blob/f71b8398e6f6eca93eb6f7cefaa940aa7e2b1f5a/base/ryu/utils.jl#L17-L19
You can see that q can be at most 21, so it's safe. The other use of pow5 is less clear:
https://github.com/JuliaLang/julia/blob/f71b8398e6f6eca93eb6f7cefaa940aa7e2b1f5a/base/ryu/exp.jl#L152-L159
Does anyone know what the range of possible values for precision and e are here? If e can be more than 27 greater than precision then we might have a problem with this change.
Bump: @oscardssmith, @quinnj do you know about the range of values that e can take here?
ah it appears that e can possibly be large if someone writes a decimal with a ton of digits.
Do you have an example?
julia> using Printf
julia> @printf "%.8g" 4.645833859177319e63 # master
4.6458338e+63
julia> @printf "%.8g" 4.645833859177319e63 # 1.8
4.6458339e+63
There are two ways that pow5 can be wrong,
- returning false when
m2is divisible bybig(5)^requiredFives - returning true when
m2is not divisible bybig(5)^requiredFivesIn the first case, for pow5 to be wrong,big(5)^requiredFivesmust be greater thantypemax(Int), butm2is always less than1<<53, and a small number is not divisible by a large number. Thusm2will never be divisible by the truebig(5)^requiredFiveswhen there is overflow and case 1 is okay.
For the second case, we need to find a requiredFives such that pow5 may wrongly return true. That is, find a requiredFives such that there exists m2 where m2 % 5^requiredFives == 0 For this to be the case, either m2 must be 0 (handled much earlier as a special case) or abs(5^requiredFives) <= m2 < 1<<53. Searching exhaustively from the cases that have overflow, findfirst(requiredFives -> abs(5^requiredFives) < 1<<53, 28:10^5)+27 == 55. Note that 5^55 < 0. If, on the other hand, we used unsinged(5)^requiredFives, then we'd have findfirst(requiredFives -> unsigned(5)^requiredFives < 1<<53, 28:10^5)+27 == 1048 and IIUC requiredFives is at most log10(floatmax(Float64)) == 308.25471555991675.
So using unsigned(5) should fix this and I benchmark it as comparable to signed exponentiation.