Skip to content
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

BigInt to Float conversions are unnecessarily slow #31293

Closed
simonbyrne opened this issue Mar 8, 2019 · 7 comments · Fixed by #31502
Closed

BigInt to Float conversions are unnecessarily slow #31293

simonbyrne opened this issue Mar 8, 2019 · 7 comments · Fixed by #31502
Labels
domain:bignums BigInt and BigFloat performance Must go faster

Comments

@simonbyrne
Copy link
Contributor

julia> using BenchmarkTools

julia> x = big"10"^300;

julia> @btime Float64($x)
  468.070 ns (6 allocations: 328 bytes)
1.0e300

julia> @btime Float64(BigFloat($x, precision=precision(BigFloat)))
  74.334 ns (2 allocations: 112 bytes)
1.0e300

This could probably be done even more efficiently by doing bit-shifting on the limbs.

@JeffBezanson
Copy link
Sponsor Member

julia> @btime Float64($x);
  287.156 ns (6 allocations: 328 bytes)

julia> @btime Float64($x, RoundToZero);
  8.383 ns (0 allocations: 0 bytes)

Yikes!

@narendrakpatel
Copy link
Contributor

I am interested in solving this issue. I experimented with Float64(::UInt128) to create efficient Float64(::BigInt). But I was not able to implement RoundNearest rounding mode. Can you provide me with some leads? @simonbyrne @JeffBezanson

@simonbyrne
Copy link
Contributor Author

simonbyrne commented Mar 11, 2019

Great!

It's a little bit tricky (which is why I suspect GMP hasn't implemented it), but one approach is:

  1. Do some preprocessing: check if the integer is too big (so would be an Inf), or if it can be converted directly via a UInt64.
  2. find the position of the highest-order bit
  3. load the 54 highest-order bits into a UInt64 (Float64 has 53 bits of precision, we add an extra one for rounding).
  4. adjust the last bit to do round-to-nearest, ties-to-even. What you would need here is check if the last two bits of the UInt64 are 01, and change them to 00 if all the remaining bits of the BigInt are 0.
  5. Convert the UInt64 to a Float64 and adjust the exponent appropriately (can use ldexp for this).

@narendrakpatel
Copy link
Contributor

I came up with this implementation

function proposed_Float64(x::BigInt)
           x == 0.0 && return 0.0
           if abs(x.size) > 16
               temp = Inf
           elseif abs(x.size) == 1
               temp = Float64(unsafe_load(x.d))
           else
               temp = Float64((UInt128(unsafe_load(x.d,abs(x.size))) << 64)
                                       +unsafe_load(x.d, (abs(x.size)-1)))
               temp *= 2.0^((abs(x.size)-2)*64)
           end
           signbit(x.size) && return -temp
           return temp
       end

By using current Float64(::BigInt) I noticed that it does not check for all the remaining bits of the BigInt. Thus this method is correct(assuming current method is correct).

julia> d = big"2"^300 + 64
2037035976334486086268445688409378161051468393665936250636140449354381299763336706183397440

julia> @btime proposed_Float64(d)
  26.645 ns (0 allocations: 0 bytes)
2.037035976334486e90

julia> @btime Float64(d)
  281.984 ns (7 allocations: 168 bytes)
2.037035976334486e90

Any suggestions? @simonbyrne

@simonbyrne
Copy link
Contributor Author

There are a couple of issues:

  1. on some platforms the limbs are only 32-bits, so you may need 3 of them to get the significant bits.
  2. this can suffer from double-rounding:
julia> x = big(2)^256 + big(2)^(256-53) + 1
115792089237316208279075339080610112188966723394941384217081534270255812050945

julia> Float64(x)
1.1579208923731622e77

julia> proposed_Float64(x)
1.157920892373162e77

@narendrakpatel
Copy link
Contributor

narendrakpatel commented Mar 27, 2019

This implementation solves the double rounding problem. I still have to clean the code and implement for 32 bits. I just wanted to know, if this type of approach is okay or not? Also in 32 bit implementation should I just use if/else or is there any better way? @simonbyrne

function Float64(n::BigInt)
    n == 0 && return 0.0
    if Int == Int64
        if abs(x.size) > 16
            temp = Inf
        elseif abs(x.size) == 1
            temp = Float64(unsafe_load(x.d))
        else
            y1 = unsafe_load(x.d, x.size)
            n = 64 - leading_zeros(y1)
            if n > 53
                y = (y1 >> (n-54))
                y = (y + 1) >> 1
                y &= ~UInt64(trailing_zeros(x) == (n-54 + (x.size-1)*64))
                d = (n+1021) << 52
                temp = reinterpret(Float64, d+y)
                temp = ldexp(temp, (x.size - 1)*64)
            else
                y = y1 << (54 - n) + unsafe_load(x.d, x.size-1) >> (10 + n)
                y = (y + 1) >> 1
                y &= ~UInt64(trailing_zeros(x) == (10+n + (x.size-2)*64))
                d = (n+1021) << 52
                temp = reinterpret(Float64, d+y)
                temp = ldexp(temp, (x.size - 1)*64)
            end
        end
        signbit(x.size) && return -temp
        return temp
    end

    #TODO: implement for 32 bit machine
end

@simonbyrne
Copy link
Contributor Author

Something along those lines was what I had in mind. Did you want to open a draft pull request? It might be easier to provide feedback on that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:bignums BigInt and BigFloat performance Must go faster
Projects
None yet
3 participants