Int Type errors

This code compiles as is with no errors.

require "big"

@[Link("gmp")]
lib LibGMP
  fun mpz_powm = __gmpz_powm(rop : MPZ*, base : MPZ*, exp : MPZ*, mod : MPZ*)
end

def powmodgmp(b, e, m)
  y = BigInt.new
  LibGMP.mpz_powm(y, b.to_big_i, e.to_big_i, m.to_big_i)
  y
end

def powmodint(b, e, m)
  r = typeof(m).new(1)
  while e > 0
    r = (r &* b) % m if e.odd?
    e >>= 1
    b = (b &* b) % m
  end
  r
end

def powmod(b, e, m)
  if typeof(m) == typeof(BigInt.new)
    powmodgmp(b, e, m)
  else
    b = b % m;  b = typeof(m).new(b)
    powmodint(b, e, m)
  end
end

def opt_int_type(n)
  if n <= UInt32::MAX
    n.to_u32
  elsif n <= UInt64::MAX
    n.to_u64
  #elsif n <= UInt128::MAX
  #  n.to_u128
  else
    n
  end
end

def powmodopt(b, e, m)
  x = opt_int_type(m)
  if typeof(x) == typeof(BigInt.new)
    powmodgmp(b, e, m)
  else
    x = x.as(UInt32 | UInt64)
    b = b % x;  b = typeof(x).new(b)
    r = powmodint(b, e, x)
    typeof(m).new(r)
  end
end

But when I add this to it to use powmodopt I get this error.

...
...
b = "329832983".to_big_i
e = 4843
m = "498422".to_big_i
puts powmodopt b,e,m

#ERROR

In newpowmodtest.cr:55:31

 55 | b = b % x;  b = typeof(x).new(b)
                                ^--
Error: wrong number of arguments for '(UInt32 | UInt64).new' (given 1, expected 0)

Overloads are:
 - Union(*T).new()
➜  crystal-projects 

I’ve consulted the docs|tutorials and tried at least a dozen ways to form the correct unions to eliminate the error, but they just create new ones.

I assume (hope) it’s something simple, but I haven’t found it.

Because the type of x is UInt32 | UInt64, it’s trying to use Union#new. Maybe you want to do like x.class.new so that it uses the runtime type of the variable, not it’s compile time type.

This will as you have noticed not work, for the reason Blacksmoke gives. There is also no benefit of doing so - the binary size of UInt32|UInt64 is actually bigger than UInt64, so the cost will be bigger than using either. Unions are also basically implemented as tagged unions and need to both fit the biggest size of the members of the union, and also there is an additional flag telling what type the value is. You can also not instantiate them directly as the language can’t know what constructor to use.

So what can you do? Well, you can create different code paths for the different sizes, for example with an if statement looking at the type and then adjust what path it takes.

That’s what I ended up doing, but it feels so inelegant.

What I want is to do the math with the smallest possible Int Type if not a BigInt.

For example, if b = "1234567890".to_big_i, the smallest fit is an Int32 to convert it to. Unfortunately, Crystal currently has bugs doing (b &* b) % m if b**2 exceeds the type range. But when that gets fixed these computations will be faster (and accurate) than always using BigInts to do all the math in.

This is intended behavior and therefore a logic error in this implementation. A correct implementation must do something else.

See the feature request I just submitted to fix this.