# UInt128 to Float64 conversion

**URL:** <https://discourse.julialang.org/t/uint128-to-float64-conversion/82317>\
**Category:** Performance\
**Created:** [June 6, 2022, 2:57pm UTC](https://discourse.julialang.org/t/uint128-to-float64-conversion/82317 "2022-06-06T14:57:22Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![albheim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albheim/32/34660_2.png) [@albheim](https://discourse.julialang.org/u/albheim)\
**Post date:** [June 6, 2022, 2:57pm UTC](https://discourse.julialang.org/t/uint128-to-float64-conversion/82317/1 "2022-06-06T14:57:23Z")

</div>

Came upon an interesting blogpost where the author explains how they implemented their own unsigned 128 bit int to 64 bit float conversion in rust just because they didn’t think it existed, and when they realised it did exist it turned out their implementation was almost twice as fast.

> **[Converting Integers to Floats Using Hyperfocus](https://blog.m-ou.se/floats/)**
>
> A few years ago, due to some random chain of events, I ended up implementing a conversion from 128 bit integers to 64 bit floats.
> This would’ve turned out to be a complete waste of time,
> except that my final version is faster than the builtin...

Curious as one is, I had a look at the julia implementation and saw it had some branches and things that might allow for this to be an improvement. So here are julias default version, the branchy version from the blog and the optimized version from the blog (pretty much, with some minor differences).

> **Code**
>
> ```julia
> function default(x::UInt128) # Default julia version
> x == 0 && return 0.0
> n = 128-leading_zeros(x) # ndigits0z(x,2)
> if n <= 53
> y = ((x % UInt64) << (53-n)) & 0x000f_ffff_ffff_ffff
> else
> y = ((x >> (n-54)) % UInt64) & 0x001f_ffff_ffff_ffff # keep 1 extra bit
> y = (y+1)>>1 # round, ties up (extra leading bit in case of next exponent)
> y &= ~UInt64(trailing_zeros(x) == (n-54)) # fix last bit to round to even
> end
> d = ((n+1022) % UInt64) << 52
> reinterpret(Float64, d + y)
> end
> 
> function simple(x::UInt128) # Branchy version from blog
> if x == 0
> return 0
> end
> n = leading_zeros(x)
> y = x << n
> exponent = (127 - n) + 1023
> mantissa = y << 1 >> 76
> dropped_bits = y << 53
> if dropped_bits > 1 << 127 || (dropped_bits == 1 << 127 && mantissa & 1 == 1)
> mantissa += 1
> end
> reinterpret(Float64, UInt64(exponent) << 52 + UInt64(mantissa))
> end
> 
> function fast(x::UInt128) # No branch (the one is optimized away) version from blog
> n = leading_zeros(x)
> y = x << n # Dont need bitrotate as in rust version
> mantissa = (y >> 75) % UInt64
> dropped_bits = (y >> 11 | y & 0xffff_ffff) % UInt64
> mantissa += (dropped_bits - (dropped_bits >> 63 & (1 - (mantissa & 1)))) >> 63
> exponent = x == 0 ? 0x0 : (1149 - n) % UInt64 
> reinterpret(Float64, exponent << 52 + mantissa)
> end
> 
> ```

> **Benchmarks**
>
> ```julia-repl
> julia> @benchmark default(q) setup=(q=rand(UInt128))
> BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
> Range (min … max): 5.030 ns … 38.663 ns ┊ GC (min … max): 0.00% … 0.00%
> Time (median): 5.516 ns ┊ GC (median): 0.00%
> Time (mean ± σ): 5.835 ns ± 0.929 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
> 
> █ ▁
> ▆▃▁▂█▄▂▃▄▄▁▄▃▄▂▂▂▂▄▂▂▂▂▃▂▂▃▂▃▃▁▂▂▁▃▃▂▂▂▁▃█▃▃▂▂▂▂▂▂▃▃▂▂▂▂▂▂ ▃
> 5.03 ns Histogram: frequency by time 7.43 ns <
> 
> Memory estimate: 0 bytes, allocs estimate: 0.
> 
> julia> @benchmark simple(q) setup=(q=rand(UInt128))
> BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
> Range (min … max): 3.587 ns … 37.554 ns ┊ GC (min … max): 0.00% … 0.00%
> Time (median): 3.727 ns ┊ GC (median): 0.00%
> Time (mean ± σ): 4.075 ns ± 0.779 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
> 
> █ ▆ ▂ ▂▄ ▁▅ ▃ ▄▂ ▇▂ ▁
> █▃█▇▂█▂██▂██▄▅█▅▄██▅▅██▅▅▄▅▆▅▅▅▅▅▆▅▅▅▅▅▄▅▅▅▅▅▆▄▅▅▆▅▅▅▄▄▅▅▅ █
> 3.59 ns Histogram: log(frequency) by time 6.46 ns <
> 
> Memory estimate: 0 bytes, allocs estimate: 0.
> 
> julia> @benchmark fast(q) setup=(q=rand(UInt128))
> BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
> Range (min … max): 3.793 ns … 37.724 ns ┊ GC (min … max): 0.00% … 0.00%
> Time (median): 4.094 ns ┊ GC (median): 0.00%
> Time (mean ± σ): 4.376 ns ± 0.782 ns ┊ GC (mean ± σ): 0.00% ± 0.00%
> 
> █
> ▂▄▂▁▂▆█▂▂▂▃▅▂▁▂▂▃▄▁▂▂▂▃▄▂▂▂▂▃▄▂▂▂▁▂▂▃▂▁▁▁▂▂▄▃▁▂▁▂▂▃▇▄▁▂▂▁▂ ▃
> 3.79 ns Histogram: frequency by time 5.27 ns <
> 
> Memory estimate: 0 bytes, allocs estimate: 0.
> 
> ```

So both versions are faster, though for some reason the simple is faster than the optimized one. And for some reason the simple version also seems to be incorrect (or at least give different answers from the other, but I assume that the current julia one is correct). Maybe this is connected to why it is fast, though I can’t see what is wrong.

```julia-repl
julia> xs = rand(UInt128, 100000);

julia> all(Float64.(xs) .== simple.(xs))
false

julia> all(Float64.(xs) .== fast.(xs))
true

```

Was thinking if this was something that was interesting for julia core? Or should it rather start in a package?

I think there are more than the UInt128 to Float64 ones that could be interesting to look at also, it seems like this and a few other int to float conversions of different sizes made it in to rust by in a PR by the same person  
[https://github.com/rust-lang/compiler-builtins/pull/464/files](https://github.com/rust-lang/compiler-builtins/pull/464/files)

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [June 6, 2022, 3:06pm UTC](https://discourse.julialang.org/t/uint128-to-float64-conversion/82317/2 "2022-06-06T15:06:48Z")

</div>

This looks like a totally reasonable implementation to me. I would not object to a PR.  
That said, we should probably go with the version presented here [https://twitter.com/m\_ou\_se/status/1524912035653468161](https://twitter.com/m_ou_se/status/1524912035653468161) as it is apparently about 2x faster.

---

<div class="post-metadata">

**Author:** ![albheim](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/albheim/32/34660_2.png) [@albheim](https://discourse.julialang.org/u/albheim)\
**Post date:** [June 6, 2022, 3:14pm UTC](https://discourse.julialang.org/t/uint128-to-float64-conversion/82317/3 "2022-06-06T15:14:05Z")

</div>

Okay, I will give it a go then, and I’ll check out the other one you found.
