2017年11月13日月曜日

暗黙の型変換

 なぞのきょどうに遭遇して結構ハマった。

#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 件のコメント:

コメントを投稿