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_$TYPE
function to deconstruct such integers.
Ok, so, to answer the question myself, what I found so far is the enif_compare
function that compares two ERL_NIF_TERM
s.
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.