読者です 読者をやめる 読者になる 読者になる

生物物理計算化学者の雛

主に科学に関する諸々を書き留めています。

1の補数と2の補数を取り違えていたせいで半日を浪費・・・

Haodop の可変長int型である VIntWritable クラスによって出力したバイナリデータをC言語で読み込むルーチンを作成したのですが、バイナリデータを数値に変換する際に、 VIntWritable は負数を2の補数でなく1の補数で出力していたことに気付かなかったため、微妙に数値が合わないという結果に陥り原因判明まで半日かかってしまいました。

当初作成したC言語でのソースコードは以下のようなものです。なお、Hadoopソースコード中にあったコメント「1バイト目が -112 〜127 の場合はそのまま1バイト長のデータ、-113〜-120ならば正数、-121-128ならば負数」と手元のVIntWritable 出力結果を見て作成したため、負数をどのように表現するのかは気にせずに作成してしまいました。

/*  bufPointer に記録されている VIntWritable 型のバイナリデータを int 型に変換する *
 *    VIntSIze には値の記録に使用されていたバイト数が代入される                    */
int getVIntFromBinary(char *bufPointer, int *VIntSize){
	int i;
	int ret;
	int length;
	char *p = bufPointer;

	if(-112 <= p[0] && p[0] <= 127){
		// single byte value
		*VIntSize = 1;
		return (int)p[0];
	}

	if(-120 <= p[0] && p[0] <= -113){
		// + value
		length = -(p[0]+112);
		ret = 0;
		for(i=0; i<length; i++){
			ret = ret << 8;
			ret = ret | (p[i+1] & 0xFF);
		}
		*VIntSize = length + 1;
		return ret;
	}

	//if(-128 <= p[0] && p[0] <= -121)   not necessary
	{
		// - value
		length = -(p[0]+120);
		ret = 0;
		for(i=0; i<length; i++){
			ret = ret << 8;
			ret = ret | (p[i+1] & 0xFF);
		}
		*VIntSize = length + 1;
		return -1 ^ ret;   /* ここを -1 * ret と間違えていた */
	}
	return 0;	
}

最初は値に -1 をかける、つまり2の補数をとることで負数に変換していました。
それに対して、実際の Hadoop ソースコード($HADOOP_HOME/common/src/java/org/apache/hadoop/io/WritableUtils.java)を確認したところ、負数を表現するために -1 をかけるのではなく、-1(16進数で FF FF FF FF) との排他的論理和(XOR)をとる、つまり1の補数を使用しています。
この違いのせいで変換後のデータが「1」だけずれてしまっており、なかなか原因を特定することができませんでした。

サブルーチンのテストをきちんとする、あるいは面倒くさがらずにソースコードをきちんと読んでおけば防げたミスでした・・・
なんとか今日中に解決できたのでよかったですが。