Performant handling of overflowing constants on 32-bit Julia Installations

A part of some of my packages have really large integer constants saved in them (reference to one page). These constants need to be stored at the highest precision possible, and then changed to a type T that a user requests. For very large constants, I use a form of

T(parse(BigInt,"...")//parse(BigInt,"..."))

to store the constants. This is okay when wanting high accuracy. However, the problem comes in when handling 32-bit installations. For those, the constants are not too reasonably large, but which will overflow are ones like this:

T(7828594302389//382182512025600)

However, if I change everything where the amount of digits can cause overflow to the parse(...) syntax, then the constants no longer inline:

function f()
  parse(Int64,"10000")
end

function g()
  10000
end

@code_llvm f()
@code_llvm g()

julia> @code_llvm f()

; Function Attrs: uwtable
define i64 @julia_f_61160() #0 {
top:
  %0 = call i64 @jlsys_parse_35934(%jl_value_t* inttoptr (i64 2147419568 to %jl_value_t*), %jl_value_t* inttoptr (i64 2228250560 to %jl_value_t*))
  ret i64 %0
}

julia> @code_llvm g()

; Function Attrs: uwtable
define i64 @julia_g_61170() #0 {
top:
  ret i64 10000
}

with

julia> @code_llvm parse(Int64,"100000")

; Function Attrs: uwtable
define i64 @jlsys_parse_35934(%jl_value_t*, %jl_value_t*) #0 {
top:
  %2 = alloca %Nullable.5, align 8
  %3 = call i64 @jlsys_endof_50451(%jl_value_t* %1)
  call void @jlsys_tryparse_internal_35940(%Nullable.5* nonnull sret %2, %jl_value_t* inttoptr (i64 2147419568 to %jl_value_t*), %jl_value_t* %1, i64 1, i64 %3, i64 0, i8 1)
  %4 = getelementptr inbounds %Nullable.5, %Nullable.5* %2, i64 0, i32 0
  %5 = load i8, i8* %4, align 8
  %6 = and i8 %5, 1
  %7 = icmp eq i8 %6, 0
  br i1 %7, label %if, label %L7

if:                                               ; preds = %top
  call void @jl_throw(%jl_value_t* inttoptr (i64 2160411152 to %jl_value_t*))
  unreachable

L7:                                               ; preds = %top
  %8 = getelementptr i8, i8* %4, i64 8
  %9 = bitcast i8* %8 to i64*
  %10 = load i64, i64* %9, align 8
  ret i64 %10
}

This performance hit may be very small “for most nontrivial problems”, but I would still like to not have any even minor dent in 64-bit performance due to 32-bit compatibility. However, I assume that like BigFloat, Int64(7828594302389) would overflow before the conversion, and so I don’t know of a nice way to handle this.

You are already parsing and creating a bunch of BigInts. Surely the cost to parse a few normal integers have to be completely negligible, I mean you still have to solve an ODE after that? Never mind @code_llvm what is the time with and without parse of the whole function you linked?

The most of the functions which do parse are for higher orders. I am hoping to keep lower order methods at least untouched. But yeah, this might be micro-optimizing. I still find the parsing very inelegant though.

I don’t follow you. When you type an integer literal, Julia automatically parses it as a type that can represent it exactly. For example, on a 64-bit machine:

julia> 382182512025600382182512025600
382182512025600382182512025600

julia> typeof(ans)
Int128

julia> 382182512025600382182512025600382182512025600382182512025600
382182512025600382182512025600382182512025600382182512025600

julia> typeof(ans)
BigInt

So, integer literals should never overflow, even on a 32-bit machine.

1 Like

Huh, I thought I tracked down errors to this. I have errors on 32-bit Windows which seem to occur due to tableaus with larger constants. But since in the tests it converts these to 64-bit floats, I can’t find out why those tests would calculate something slightly differently, unless there was overflow here.

Is there an easy way to make Julia “act” like it’s 32-bit? I tried doing a 32-bit install but I couldn’t get SymEngine.jl to build correctly for 32-bit Julia binaries on 64-bit Windows, likely because the Python dependency didn’t match.