library(bit64)
はじめに
「integer64
で定義されたベクトルをmatrix
に変換したら値がおかしくなった」という話を聞いたので原因を調査してみました。あえて結論を最初に書かないので、忙しい人はまとめを参照してください。
再現可能な例
<- as.integer64(c(1, 2, 3))
int64 int64
integer64
[1] 1 2 3
|>
int64 as.matrix()
[,1]
[1,] 4.940656e-324
[2,] 9.881313e-324
[3,] 1.482197e-323
わけがわからないくらい小さな数字に変化してしまっています。暇な人はスクロールを止めて原因を考えてみましょう。
integer64ってなんだ
なんとなくinteger64
が怪しそうだし、numeric
に変換すれば直るんじゃね?wくらいのノリでコメントしたら予想通り解決しました。
|>
int64 as.numeric() |>
as.matrix()
[,1]
[1,] 1
[2,] 2
[3,] 3
原因調査
検索すると似たような問題で躓いている人が他にも結構いるみたいです1。動けばOK!wを卒業したいという気持ちもあるので原因を考えていきます。
numericに変換すると期待通りに動作する理由を考える
まずはas.numeric
が何をやっているかを見ていきます。
as.numeric
function (x, ...) .Primitive("as.double")
as.numeric
はas.double
を呼んでいるんですね。bit64
を読み込んだときにas.double
がマスクされていたのでメソッドを確認してみます。
getS3method("as.double", "integer64")
function (x, keep.names = FALSE, ...)
{
ret <- .Call(C_as_double_integer64, x, double(length(x)))
if (keep.names)
names(ret) <- names(x)
ret
}
<bytecode: 0x00000128027d9408>
<environment: namespace:bit64>
.Call
って何?という気持ちになります。ドキュメントを見ると内部関数を読み込むものらしいです。bit64
のソースコードがGitHubに置いてあったので2検索を掛けてみるとそれっぽい関数がヒットしました。
(SEXP x_, SEXP ret_){
SEXP as_double_integer64long long i, n = LENGTH(x_);
long long * x = (long long *) REAL(x_);
double * ret = REAL(ret_);
double rmax = pow(FLT_RADIX, DBL_MANT_DIG) - 1;
double rmin = -rmax;
= FALSE;
Rboolean naflag for (i=0; i<n; i++){
if (x[i]==NA_INTEGER64)
[i] = NA_REAL;
retelse{
if (x[i]<rmin || x[i]>rmax)
= TRUE;
naflag [i] = (double) x[i];
ret}
}
if (naflag)warning(INTEGER64_TODOUBLE_WARNING);
return ret_;
}
本質的にはret[i] = (double) x[i];
の行でlong long
型をdouble
型にキャストしているだけっぽいですね。 これでうまくいく理由はわかりました。次は上手くいかない方の理由を考えていきます。
matrixはなにをしているか
matrix
のメソッドから確認してみましょう。
methods("matrix")
no methods found
なかったです。次に中身を見てみることにします。
matrix
function (data = NA, nrow = 1, ncol = 1, byrow = FALSE, dimnames = NULL)
{
if (is.object(data) || !is.atomic(data))
data <- as.vector(data)
.Internal(matrix(data, nrow, ncol, byrow, dimnames, missing(nrow),
missing(ncol)))
}
<bytecode: 0x000001287114f568>
<environment: namespace:base>
またC言語の予感。ぐえ~。
同様に実装を検索してみたところ、みつかりましたが結構長いです3。 型まわりが怪しいはずなのでまずはこの部分を読んでみます。
switch(TYPEOF(vals)) {
case LGLSXP:
case INTSXP:
case REALSXP:
case CPLXSXP:
case STRSXP:
case RAWSXP:
case EXPRSXP:
case VECSXP:
break;
default:
(_("'data' must be of a vector type, was '%s'"),
error(TYPEOF(vals)));
R_typeToChar}
TYPEOF()
4でmatrix()
の引数の型をチェックしていそうです。また、行列を作成するためにTYPEOF(vals)
とおなじ型でメモリを確保しています。
PROTECT(ans = allocMatrix(TYPEOF(vals), nr, nc));
そういえばRにもtypeof()
が実装されていた気がします。確認してみましょう。
typeof
function (x)
.Internal(typeof(x))
<bytecode: 0x0000012873086568>
<environment: namespace:base>
matrix()
で呼んでいる内部関数と同じっぽいですね。integer64型のtypeを確認してみます。
|>
int64 typeof()
[1] "double"
おやおや。double
型が出てきました。どうもmatrix
はinteger64
をdoubele
型として解釈しているようです。こいつが悪さしていそうに思えます。次はinteger64
をdoubele
型として解釈するとどうなるのかを考えていくことにします。
integer64型の値をdoubel型として解釈してみる
C言語の授業を思い出す時間がやってきました。integer64
、いわゆるlong long int
は先頭に1bitの符号ビット、残りの63bitが値を表す部分です。整数の1を内部表現で表すと次のようになります。
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
この内部表現をdouble
型として解釈するとどうなるか考えます。double
型は1bitの符号ビット、11bitの指数部、52bitの仮数部で構成されます。今回のケースだと次の通りです。
符号ビット: 0 (+) 指数部: 00000000000 (?) 仮数部: 0000000000000000000000000000000000000000000000000001
指数部が全て0になることなんてあったっけ?となってしらべてみたら、ありました(無知)。非正規化数と呼ぶらしいです5。 Wikipediaに記載された定義通り計算してみます。
\[ \begin{align*} 値 &= 符号 \cdot 2^{指数部} \cdot 仮数部 \\ &= (+1) \cdot 2^{-1022} \cdot (1.0 \cdot 2^{-52}) \\ &= 2^{-1022} \cdot 2^{-52} \\ &= 2^{-1074} \end{align*} \]
2^(-1074)
[1] 4.940656e-324
勝ちです。
まとめ
- integer64型はdouble型で実装されている
- bit64の読み込み時に多くの関数はinteger64型に対応するメソッドが追加される
- matrixはメソッドがなく、引数の型をもとにしてRの内部関数で処理を行う
- 結果として、matrixの内部でinteger64型をdouble型と誤認してしまうため大きく異なる値に変換されてしまう
matrixに限った問題ではないと思います。integer64型を利用するときは気を付けましょう!
Footnotes
https://stackoverflow.com/questions/70365850/as-integer-on-an-int64-dataframe-produces-unexpected-result↩︎
https://github.com/cran/bit64/blob/53a5d8eaf9ef55554fd44f91396f51745dc8243d/src/integer64.c#L128-L146↩︎
https://github.com/wch/r-source/blob/trunk/src/main/array.c#L81-L228↩︎
https://github.com/wch/r-source/blob/531d0fd05d3bdc0f8a0350e3ecd3e101d315ab59/src/include/Defn.h#L225↩︎