算数と違うC言語の足し算
~10進数と16進数で結果が変わる~

■この記事の概要

この記事では、C言語で発生する計算結果の不一致について解説しています。具体例として、0xFFFFffff + 1の桁あふれや、浮動小数点計算の順序により異なる結果が生じるケースを取り上げています。また、接尾子Lを活用することで桁あふれを回避する方法を詳述し、安全な数値操作を推奨しています。


足し算の基本的な使い方

//基本的な足し算
#include <stdio.h>
int main(void){ 
    printf("足し算 1+1=%d\n",1+1);

    int a = 10;
    int b = 1;
    a = a + b;
    printf("算術+演算子 %d\n",a);
    
    a = 100;
    a += b;
    printf("+=代入演算子 %d\n",a);
    
    a = 1000;
    printf("後置演算子++ %d\n",a++);

    a = 10000;
    printf("++前置演算子 %d\n",++a);
}
./a.out
足し算 1+1=2
算術+演算子 11
+=代入演算子 101
後置演算子++ 1000
++前置演算子 10001

C言語の足し算はだいたい算数と同じですが
違いもあるので注意が必要です。


■足し算には桁あふれの危険がある

//C言語での桁あふれ
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
int main(void){
    uint32_t     x = UINT_MAX ;
    uint64_t     y = x + 2 ;
    uint64_t     z = x + 2L ;
    
    printf("y = %lx\n",y);
    printf("z = %lx\n",z);
}
$ ./a.out
1
100000001

y = x + 2 ;と記述すると右辺をint型で計算するため桁あふれします。

y = x + 2L ;と記述すると右辺をlong型で計算するため桁あふれしません。


■16進数と10進数で結果が変わる

注:LP64-gcc環境とします。

#include <stdio.h>
int main(void){
    printf("%lx\n",0xFFFFffff+1);
    printf("%lx\n",4294967295+1);
}
./a.out
0
100000000

筆者も間違えました


■浮動小数点は足し算の順番で結果が変わる

#include <float.h>
#include <stdio.h>

int main(void) 
{
    long double  ゴジラ = FLT_MAX;  // 巨大
    long double  人間   = 1.0L;     // 比較的小さな数
    long double  コング = -FLT_MAX; // 巨大

    long double  人間踏まれる = ゴジラ + 人間 + コング ;

    long double  人間生きてる = ゴジラ + コング + 人間 ;

    printf("人間踏まれる = %.20Lf\n", 人間踏まれる);
    printf("人間生きてる = %.20Lf\n", 人間生きてる);
}
./a.out 
人間踏まれる = 0.00000000000000000000
人間生きてる = 1.00000000000000000000

この例を簡単に例えると、
人間踏まれるの計算では
ゴジラ対人間は、人間が踏まれて誤差に丸め込まれます。
人間生きてるの計算では
ゴジラ対コングは0になり、後から人間を足すので1.0が残ります。


sum += x が sum = sum + x より好ましい理由

//sum += x が sum = sum + x より好ましい理由
int Long_and_complex_expressions = 0;
#include <stdio.h>
void OK(void){
    int sum = 0;
    for(int i = 1;i<=10;i++){
        sum += i;
    }
    printf("OK:sum = %d\n",sum);
}
void NG(void){
    int Long_and_complex_expressions_sum = 0;    
    for(int i = 1;i<=10;i++){
        Long_and_complex_expressions_sum 
            = Long_and_complex_expressions + i;
    }
    printf("NG:sum = %d\n",Long_and_complex_expressions_sum);
}
int main(void){
    OK();
    NG();
}
./a.out
OK:sum = 55
NG:sum = 10


OK関数は意図した1から10の合計値55になりますが、
NG関数は意図した結果になりません。

sum += x が
sum = sum + x より好ましい理由は
このサンプルのように
長い複雑な式を2度見なくて済むからです。


参考:

DCL16-C. long 値を表すには小文字の “l” ではなく大文字の “L” を使う