ZACKYこと山崎進です。
Elixir/NIF間でBigNumをやりとりするの3回目は,具体的な使用例を説明したいと思います。
コード例
型多相かつ型安全なNIFで紹介したコード例を一部改変します。
def asm_1(a, b) do
OK.try do
result <- case {a, b} do
{a, b} when is_int64(a) and is_int64(b) -> asm_1_nif_ii(a, b)
{a, b} when is_uint64(a) and is_uint64(b) -> asm_1_nif_uu(a, b)
{a, b} when is_integer(a) and is_integer(b) -> asm_1_nif_bb(Asm.BigNum.from_int(a), Asm.BigNum.from_int(b))
{a, b} when is_int64(a) and is_float(b) -> asm_1_nif_if(a, b)
{a, b} when is_uint64(a) and is_float(b) -> asm_1_nif_uf(a, b)
{a, b} when is_integer(a) and is_float(b) -> asm_1_nif_bf(Asm.BigNum.from_int(a), b)
{a, b} when is_float(a) and is_int64(b) -> asm_1_nif_fi(a, b)
{a, b} when is_float(a) and is_uint64(b) -> asm_1_nif_fu(a, b)
{a, b} when is_float(a) and is_integer(b) -> asm_1_nif_fb(a, Asm.BigNum.from_int(b))
{a, b} when is_float(a) and is_float(b) -> asm_1_nif_ff(a, b)
_ -> {:error, :arithmetic_error}
end
after
case result do
x when is_number(x) -> x
x when is_tuple(x) -> Asm.BigNum.to_int(x)
end
rescue
:arithmetic_error -> raise ArithmeticError, message: "bad argument in arithmetic expression"
end
end
static
ERL_NIF_TERM asm_1_nif_bb(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
unsigned a_neg, b_neg;
VECTOR *a_v, *b_v;
if(__builtin_expect((enif_get_big_num(env, argv[0], &a_neg, &a_v) == 0), 0)) {
return arithmetic_error;
}
if(__builtin_expect((enif_get_big_num(env, argv[1], &b_neg, &b_v) == 0), 0)) {
return arithmetic_error;
}
ERL_NIF_TERM result = enif_make_big_num(env, a_neg, a_v);
enif_free(a_v->value);
enif_free(a_v);
enif_free(b_v->value);
enif_free(b_v);
return enif_make_tuple2(env, ok_atom, result);
}
asm_1_nif_bf
,asm_1_nif_fb
も同様に実装します。
次のようなコードを走らせてみます。
iex> IO.puts "#{Asm.max_uint + 1} + 1 = #{asm_1(Asm.max_uint + 1, 1)}"
18446744073709551616 + 1 = 18446744073709551616
:ok
加算はまだ実装していないので,計算結果としては合っていませんが,BigNum を NIF で送受信できていることがわかります。
BigNum の加算や浮動小数点数への変換の実装については,C言語で書くのは面倒なので,言語処理系 micro Elixir / ZEAM の実装が進んでからに先送りしたいと思います。