#include#include int main(void) { { int32_t v1 = -104; uint32_t v2 = 32768; int32_t v3 = 450869; int32_t v4 = v1 * v2 * 32 / v3; printf("1:%d\n", v4); } { int32_t v1 = -104; int32_t v2 = 32768; int32_t v3 = 450869; int32_t v4 = v1 * v2 * 32 / v3; printf("2:%d\n", v4); } return(0); }
というコード。最初のブロックは当初のコード、次のブロックは修正後のコード。値は実際に使われた値を書いている。
どちらも、結果は-241を予想している。
-104 * 32768 * 32 / 450869 = 約-241.870で、切り捨てて-241となる。関数電卓とかで計算しても、このような結果になる。
しかし、実際には1では9284という結果になる。
arm-none-eabi-gcc 4.9.3でも、WSLのgcc 4.8.4でも同じ結果。
これは、符号ありと符号なしで演算を行う場合、符号なしに変換されて計算されるためらしい(よくわかってない)。
-104はu32で0xFFFFFF98となる。これに32768を掛けると0x7FFFFFCC0000となるが、32bitに切り落とされるので0xFFCC0000となる。32を掛けて同0xF9800000、450869で割って9284となる。
型変換が行われるのは、違う形で演算を行うためであり、同じ型同士であれば変換は行われない。ということで、v2をs32にすれば正しい結果となる。
JSF++で、「符号付きおよび符号なしの値は、算術演算または比較演算で混在してはなりません(理論的根拠: 符号付き値と符号なし値を混在させると、演算が多数の算術変換および積分型昇格のルールに従うため、エラーが発生しやすくなります)」(AV Rule 162 shall not)というのがある。
今回、身をもって実感した。
ついこの間、JSF++を翻訳してたので、そう言えば混在しちゃいけないルールが有ったな、と思い出したけど、これを読んでなかったらもっと深みにハマってた気がする。
このv2は、元のコードでは、ハードウェアの符号なし32bitレジスタから読み取った値なので、そのままu32に突っ込んでいた。実際に取りうる範囲は0x00000000-0x0000FFFFなので、s32でも足りたのだが。
0 件のコメント:
コメントを投稿