[C言語] Cパズルブック 2.3 その他の型変換

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問2.3のコードと出力は以下の通りです。

ここで一旦Cパズルブックを使ったプログラミング学習を休止して、アプリ製作に戻ります。

#include "stdio.h"

#define PR(x) printf("x = %g\t", (double)x)
#define NL putchar('\n')
#define PRINT1(x1) PR(x1); NL
#define PRINT2(x1, x2) PR(x1); PRINT1(x2)

int main() {
    double d = 3.2, x;
    int i = 2, y;

    x = (y = d/i)*2; 
    PRINT2(x, y);
    // x = (y = (d/i))*2
    // x = (y = (int)1) *2
    // x = 1*2

    y = (x = d/i)*2; 
    PRINT2(x, y);
    // y = (x = (d/i))*2
    // y = (x = (double)1.6 *2
    // y = 3


    y = d * (x = 2.5/d);
    PRINT1(y);
    // y = d * (x = (2.5/3.2))
    // y = 3.2 * 2.5/3.2
    // y = 2

    x = d * (y = ((int)2.9 + 1.1)/d);
    PRINT2(x, y);
    // x = d * (y = ((int)2 + 1.1)/d);
    // x = d * (y = 3.1/3.2);
    // x = d * (y = 0);

    return 0;
}
x = 2	x = 1	
x = 1.6	x = 3	
x = 2	
x = 0	x = 0	

[C言語] Cパズルブック 2.2.4-6 整数と浮動小数点数の型変換

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問2.2.4-6のコードと出力は以下の通りです。

特記すべきことはありません。

少しダレてきたので、他の教本に一旦移る予定です。Cパズルブックは第1章、第6章、第2章を順に完遂する形となり、キリとしては特に問題ないでしょう。何らかのタイミングでまた戻ってきます。

#include "stdio.h"

#define PR(x) printf("x = %.8g\t", (double)x)
#define NL putchar('\n')
#define PRINT4(x1, x2, x3, x4) PR(x1); PR(x2); PR(x3); PR(x4); NL

int main() {
    double d;
    float f;
    long l;
    int i;

    // 問2.2.4
    d = f = l = i = (double)100/3;
    // (d = (f = (l = (i = (double)100/3))))
    // (d = (f = (l = ((integer)100/3)))) and i=33
    // (d = (f = (long)33)) and l=33
    // (d = (float)33) and f=33
    // ((double)33) and d=33
    PRINT4(i, l, f, d);

    // 問2.2.5
    i = l = f = d = (double)(100000/3);
    // (i = (l = (f = (d = (33333)))))
    // (i = (l = (f = (double)33333))) and d=33333
    // (i = (l = (float)33333)) and f=33333
    // (i = (long)33) and l=33333
    // ((integer)33) and i=33333
    PRINT4(i, l, f, d);

    // 問2.2.6
    d = f = l = i = 100000/3;
    // (d = (f = (l = (i = 100000/3))))
    // (d = (f = (l = ((integer)33333)))) and i=33333
    // (d = (f = (long)33)) and l=33333
    // (d = (float)33) and f=33333
    // ((double)33) and d=33333
    PRINT4(i, l, f, d);

    return 0;
}
x = 33	x = 33	x = 33	x = 33	
x = 33333	x = 33333	x = 33333	x = 33333	
x = 33333	x = 33333	x = 33333	x = 33333

[C言語] Cパズルブック 2.2.2-3 整数と浮動小数点数の型変換 IEEE754

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問2.2.2-3のコードと出力は以下の通りです。

doubleからfloatへの変換で最終桁が3から2に変わっていますが、これは64ビットから32ビットへの精度低下によるものです。

各変数がメモリにどう格納されているのかLLDBデバッガで確認しました。doubleとfloatはIEEE754内部表現に変換されて格納されています。ちなみにIEEEは米国電気電子学会を指します。

#include "stdio.h"

#define PR(x) printf("x = %.8g\t", (double)x)
#define NL putchar('\n')
#define PRINT4(x1, x2, x3, x4) PR(x1); PR(x2); PR(x3); PR(x4); NL

int main() {
    double d;
    float f;
    long l;
    int i;

    d = f = l = i = 100/3;
    // (d = (f = (l = (i = (100/3)))))
    // (d = (f = (l = (i = 33))))
    // (d = (f = (l = (integer)33))) and i=33
    // (d = (f = (long)33)) and l=33
    // (d = (float)33) and f=33
    // ((double)33) and d=33
    PRINT4(i, l, f, d);

    l = i = f = d = 100/3.;
    // (l = (i = (f = (d = (100/3)))))
    // (l = (i = (f = (d = 33))))
    // (l = (i = (f = (double)33))) and d=33.333333
    // (l = (i = (float)33)) and f=33.333332
    // (l = (integer)33) and i=33
    // ((long)33) and l=33
    PRINT4(i, l, f, d);

    return 0;
}
x = 33	x = 33	x = 33	x = 33	
x = 33	x = 33	x = 33.333332	x = 33.333333	

double d : 4040aaaaaaaaaaab (IEEE754内部表現) = 33.333333
float f : 42055555 (IEEE754内部表現) = 33.333332
int i : 21 (16進数) = 33
long l : 21 (16進数) = 33
※格納方式はリトルエンディアンのため右から読む

[C言語] Cパズルブック 2.2.1 整数と浮動小数点数の型変換 除算演算子 “/”

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問2.2.1のコードと出力は以下の通りです。

整数同士の割り算では計算結果が整数(商)になる、というルールについてC++20規格での記載箇所をアップしておきます。たまに扱う度に引っかかるところなので規格上の根拠を確認しておきました。

要は(a/b)*b + a%b=aになるよう言語が設計されているということを意味します。商と剰余で元の整数を算出できます。

#include "stdio.h"

#define PR(x) printf("x = %.8g\t", (double)x)
#define NL putchar('\n')
#define PRINT4(x1, x2, x3, x4) PR(x1); PR(x2); PR(x3); PR(x4); NL

int main() {
    double d;
    int d2;
    float f;
    long l;
    int i;

    d = 100/3;
    d2 = 100%3;
    printf("d = %.8g\n",d);
    printf("d2 = %d\n",d2);

    int a;
    int b;
    double c;
    a = 100;
    b = 3;
    c = (a/b)*b + a%b;
    printf("c = %.8g\n",c);

    i = l = f = d = 100/3;
    // (i = (l = (f = (d = (100/3)))))
    // (i = (l = (f = (d = 33))))
    // (i = (l = (f = (double)33))) and d=33
    // (i = (l = (float)33)) and f=33
    // (i = (long)33) and l=33
    // ((integer)33) and i=33
    PRINT4(i, l, f, d);

    return 0;
}
d = 33
d2 = 1
c = 100
x = 33	x = 33	x = 33	x = 33	
除算に関する記述(C++20規格)

[C言語] Cパズルブック 2.1.2 文字型, 文字列型, 整数型 シフト演算子

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問2.1.2のコードと出力は以下の通りです。

#include "stdio.h"
#define PRINT(format, x) printf("x = %"#format"\n",x)

int main() {
    int sx = -8;
    unsigned ux = -8;

    PRINT(o, sx);
    PRINT(o, ux);

    PRINT(o, sx >> 3);
    PRINT(o, ux >> 3);

    PRINT(d, sx >> 3);
    PRINT(d, ux >> 3);

    return 0;
}
x = 37777777770
x = 37777777770
x = 37777777777
x = 3777777777
x = -1
x = 536870911
-8 = 11111111111111111111111111111000
8進数:37777777770

3ビット右シフト
符号あり
11111111111111111111111111111111 右算術シフト(空白を符号ビット1で埋める)
8進数:37777777777(11桁)
10進数:-1

符号なし
00011111111111111111111111111111 論理シフト(空白を0で埋める)
8進数:3777777777(10桁)
10進数:536870911

[C言語] Cパズルブック 2.1.1 文字型, 文字列型, 整数型

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

難しかった第6章が終わり、前に戻って第2章から始めます。

問2.1.1のコードと出力は以下の通りです。

整数5は文字列としてはASCIIコード35(16進数, 10進数では53)になります。

LLDBデバッガで確認したところ、最初の2変数はスタック領域に整数やASCIIコードとして格納されていますが、stringはポインタなのでメモリアドレスが格納されていました。文字列”5″はスタック領域の前の方に格納されていました。

#include "stdio.h"
#define PRINT(format, x) printf("x = %"#format"\n",x)

int integer = 5;
char character = '5';
char *string = "5";

int main() {
        PRINT(d, string);
        PRINT(x, string); // 追加
        PRINT(x, &string); // 追加
        printf("\n");

        PRINT(d, character);
        PRINT(d, integer);
        PRINT(s, string);
        PRINT(c, character);
        PRINT(c, integer=53);
        PRINT(d, ('5' > 5));

    return 0;
}
x = 10420060
x = 9eff5c
x = 9f4008

x = 53
x = 5
x = 5
x = 5
x = 5
x = 1 // 53(10進数) > 5はTRUE

[C言語] Cパズルブック 6.4 ポインタによる混乱

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.4のコードと出力は以下の通りです。

インクリメント++やデクリメント–では配列の基準点が移動しますが、添字では移動せず読み取りだけであるというところがポイントです。教本P151以降に詳しい解説があります。

ポインタの特徴を生かしつつ極力ややこしくした問題です。アスタリスクを3つも並べるに至っては狂気の沙汰です。このような問題でポインタ嫌いになりC言語から離れていった方が量産されたのではないでしょうか。

ただ当時のメモリやストレージの貧弱さを考えるとこのような手法はおふざけではなく真面目に使われることもあったのでしょう。

#include "defs.h"

char *c[] = {
        "ENTER",
        "NEW",
        "POINT",
        "FIRST"
        };

char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

int main() {
        printf("%s\n", **++cpp);
        // *(*(++CPP))
        // 1.++CPP
        // cppで配列cpの2番要素c+2へ移る
        // 2.*(++CPP)
        // 配列cのc+2へ移る
        // 3.*(*(++CPP))
        // 参照先の格納文字列"POINT"を指す

        printf("%s\n", *--*++cpp + 3);
        // *(--(*(++cpp)))
        // 1.++cpp
        // cppで3番要素c+1へ移る
        // 2.*(++cpp)
        // 配列cのc+1へ移る
        // 3.--(*(++cpp))
        // 配列cのcへ移る
        // 4.*(--(*(++cpp)))
        // 文字列"ENTER"の先頭へ移る
        // 5.*(--(*(++cpp))) + 3
        // "ENTER"の4番要素以降"ER"を指す

        printf("%s\n", *cpp[-2] + 3);
        // 1.cpp[-2]
        // cppで3番要素から1番要素c+3へフォーカスする。移動はせず基準は3番要素c+1のまま
        // 2.*cpp[-2]
        // 配列cのc+3は"FIRST"
        // 2.*cpp[-2] + 3
        // "FIRST"の4番要素以降"ST"を指す

        printf("%s\n", cpp[-1][-1] + 1);
        // 1.cpp[-1]
        // cppで3番要素c+1から2番要素c+2へフォーカスする。基準は3番要素c+1のまま
        // 2.cpp[-1][-1]
        // 配列cのc+2から1つ左へシフトしてc+1をフォーカスする
        // 3.cpp[-1][-1] + 1
        // c+1である"NEW"の2番要素以降"EW"を指す

    return 0;
}
POINT
ER
ST
EW

[C言語] Cパズルブック 6.3 多次元配列

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.3のコードと出力は以下の通りです。

取り立てて難しいところはありませんでした。LLDBデバッガで2次元配列、1次元配列のメモリ格納値を確認しました。

#include "defs.h"

int a[3][3] = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}};
int *pa[3] = {
            a[0], a[1], a[2]};
int *p = a[0];

int main() {
    int i;
    for(i = 0; i < 3; i++){
        PRINT3(d, a[i][2-i], *a[i], *(*(a+i)+i));
    }
    NL;
    
    for(i = 0; i < 3; i++){
        PRINT2(d, *pa[i], p[i]);
    }

    return 0;
}
value = 3	value = 1	value = 1	
value = 5	value = 4	value = 5	
value = 7	value = 7	value = 9	

value = 1	value = 1	
value = 4	value = 2	
value = 7	value = 3
ピンクは整数の2次元配列
緑は各1次元配列の先頭を示すポインタの配列
青は2次元配列の先頭を示すポインタ

[C言語] Cパズルブック 6.2.1-2 ポインタの配列 ポインタのポインタ

『Cパズルブック』(Alan R.Feuer, 1985)
[Mac M2 Pro 12CPU, macOS Ventura 13.3.1, clang 14.0.3]

問6.2.1-2のコードと出力は以下の通りです。フォーマット指定子は10進数dから16進数xに変更しています。

ポインタのポインタを使うことでデータのアドレスを動的に変更でき、変数を効率的に共有できるようになります。

#include "defs.h"

int a[] = {0, 1, 2, 3, 4};
int *p[] = {a, a+1, a+2, a+3, a+4};
int **pp = p;

int main() {
    PRINT2(x, a, *a);
    PRINT3(x, p, *p, **p);
    PRINT3(x, pp, *pp, **pp);

    printf("&pp: %x\n", &pp); //「ポインタのポインタ」アドレス

    return 0;
}
value = 4c04000	value = 0	
value = 4c04018	value = 4c04000	value = 0	
value = 4c04018	value = 4c04000	value = 0	
&pp: 4c04040

配列aのアドレス: 4c04000、格納データ: {0,1,2,3,4}
「ポインタ」アドレス: 4c04018、格納データ: 4c04000
「ポインタのポインタ」アドレス: 4c04040、格納データ: 4c04018

「ポインタ」アドレスの格納データ4c04000を動的に変更して変数を効率的に共有する。