Convert to integer type of minimum required size

I thought that this should be a trivial question, but I’m failing to find the answer. How can I convert an arbitrary value for which isinteger(x) is true, into the subtype of Signed or Unsigned with the minimum required size?

For instance, if I want a Signed this should be Int64 for 2e10, Int128 for 2e19, etc.

I hoped that the Signed constructor would do this, but it seems to be equivalent to Int (and Unsigned seems to be the same as UInt), so it doesn’t work.

For floating point numbers, there is the float function that converts any number to an appropriate subtype of AbstractFloat, but I didn’t find an equivalent solution for integers.

Doing a conversion like this is type-unstable (because it depends on the value, not the type), so you wouldn’t normally want to do it in performance-sensitive code. And if you have performance-insensitive code, you might as well just use BigInt.

That being said, you could certainly do something like this in principle:

function tointeger(x::AbstractFloat)
    for T in (Int8, Int16, Int32, Int64, Int128)
        if typemin(T) ≤ x ≤ typemax(T)
            return T(x)
    return BigInt(x)

But as I said, doing type-unstable conversions like this will result in slow code.

Why do you want to do this?


Well, I do not know why heliosdrm had to it, but it is certainly necessary sometimes. As pointed in previous discussions, if one wants an exact determinant of an integer matrix, one has often to convert
the matrix to BigInt. If the determinant happens to be a normal integer, then converting it to Int
will certainly speed up the rest of the computation…

Actually nothing serious; I was just experimenting with conversions between numeric types, and noticed this lack of an equivalent to float, which as pointed out by @Jean_Michel might be convenient in some situations. But the problem of type stability makes a lot of sense of course, so I understand that the advice is: for arbitrary size just use BigInt (well, or SafeIntegers maybe).

Yet, looking into the precision limits of different types, I can see that there are some constraints that can be used to define type-stable (partial) solutions to this problem:

  • If a number is an integer approximated in a Float16, it can always be represented as an Int32, since Float16(typemax(Int32)) == Inf16 (and for typemin it is -Inf16).
  • Integer approximations in Float32 or higher can only be expressed in full by BigInt.
  • Exact integers represented by Float16 (smaller than maxintfloat(Float16)) can be represented by Int16; and same for Float32/Int32, and Float64/Int64.

Float16(6.5504e4) == 65504 is an exact integer. maxintfloat(Float16) is only the largest consecutive integer that is exactly represented in Float16.


You mean UInt16 right? Because:

julia> trunc(Int16, prevfloat(typemax(Float16)))
ERROR: InexactError: trunc(Int16, 65504.0)
 [1] trunc at ./float.jl:682 [inlined]
 [2] trunc(::Type{Int16}, ::Float16) at ./float.jl:360
 [3] top-level scope at REPL[12]:1


julia> trunc(UInt16, prevfloat(typemax(Float16)))


julia> trunc(UInt32, prevfloat(typemax(Float32)))
ERROR: InexactError: trunc(UInt32, 3.4028235e38)
 [1] trunc(::Type{UInt32}, ::Float32) at ./float.jl:682
 [2] top-level scope at REPL[14]:1

julia> trunc(UInt64, prevfloat(typemax(Float64)))
ERROR: InexactError: trunc(UInt64, 1.7976931348623157e308)
 [1] trunc(::Type{UInt64}, ::Float64) at ./float.jl:682
 [2] top-level scope at REPL[15]:1

So I do not believe this holds for larger Floating Point numbers for which the exponent is bigger.

No, I meant Int16, but due to an incomplete understanding of maxintfloat, as pointed out by @stevengj .