celixirbigintegerffierlang-nif

How to deal with big integers in Elixir NIFs


I wrote an implementation of quicksort in C as an Elixir NIF. I'm aware that there is a BIF for that, I'm just doing this to teach myself NIFs.

Now my code works so far, that is, I can call MyQuicksort.my_quicksort([1,100,2]) and get [1,2,100] returned as expected. However, elixir integers can be arbitrarily large and if I call MyQuicksort.my_quicksort([123, 67_845_734_623_721_230_492_834, 1]) I get ~c"get_int gone wrong". This is my own error message but the point is that enif_get_long does not succeed for the large integer value. On https://www.erlang.org/doc/man/erl_nif all enif_get_$TYPE functions for integer types have some hint like "Returns false if term is outside the bounds of the type".

My question is therefore if there is anyway to deal with such integers inside a NIF function. Here is the code that I use to turn the elixir list into a C array.

static ERL_NIF_TERM my_quicksort(ErlNifEnv *env, int argc,
                                 const ERL_NIF_TERM argv[]) {
  uint size;
  if (!enif_get_list_length(env, argv[0], &size))
    return enif_make_string(env, "get_list_cell gone wrong", ERL_NIF_UTF8);
  if (size > INT_MAX)
    return enif_make_string(env, "list size too large", ERL_NIF_UTF8);
  long n[size];
  ERL_NIF_TERM head;
  ERL_NIF_TERM old_tail = argv[0];

  ERL_NIF_TERM new_tail;

  for (int i = 0; i < size; i++) {
    if (!enif_get_list_cell(env, old_tail, &head, &new_tail))
      return enif_make_string(env, "get_list_cell gone wrong", ERL_NIF_UTF8);
    if (!enif_get_long(env, head, &n[i]))
      return enif_make_string(env, "get_int gone wrong", ERL_NIF_UTF8);

    old_tail = new_tail;
  }

  quicksort(n, 0, size - 1);

I am aware why my program behaves the it does as I described. I feel like I am just missing the right enif_get_$TYPEfunction to deconstruct such integers.


Solution

  • Ok, so, to answer the question myself, what I found so far is the enif_compare function that compares two ERL_NIF_TERMs.

    That means I just have to transform the ERL_NIF_TERM that represents the list to sort into an array containing all its elements. Here is how the part of my code I listed in my answer looks after I incorporated this idea.

    static ERL_NIF_TERM my_quicksort(ErlNifEnv *env, int argc,
                                     const ERL_NIF_TERM argv[]) {
      uint size;
    
      if (!enif_get_list_length(env, argv[0], &size))
        return enif_make_string(env, "get_list_cell gone wrong", ERL_NIF_UTF8);
      if (size > INT_MAX)
        return enif_make_string(env, "list size too large", ERL_NIF_UTF8);
    
      ERL_NIF_TERM n[size];
      ERL_NIF_TERM head;
      ERL_NIF_TERM old_tail = argv[0];
      ERL_NIF_TERM new_tail;
    
      for (uint i = 0; i < size; i++) {
        if (!enif_get_list_cell(env, old_tail, &n[i], &new_tail))
          return enif_make_string(env, "get_list_cell gone wrong", ERL_NIF_UTF8);
    
        old_tail = new_tail;
      }
    
      quicksort(n, 0, size - 1);
    
      return enif_make_list_from_array(env, n, size);
    

    Then just have to adjust accordingly the types used in the quicksort function as well as the sort function it uses.

    Note that if you forget to actually use enif_compare(x,y) > 0 instead x > y you don't get an error, but still do not get the desired behavior for big numbers.

    After I did this I can for example call

    MyQuicksort.my_quicksort([
        123,
        97_845_734_623_721_230_492_834,
        67_845_734_623_721_830_492_834,
        111,
        67_845_734_623_721_230_492_111,
        9909
      ])
    

    and actually get the sorted list back.