何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『コンピュータ・システム』読書記録

図 2.4 のコード

オブジェクトのバイト表現を表示するコード。 ポインタのキャストを活用している。

#include <stdio.h>
#include <string.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len){
    size_t i;
    for (i = 0; i < len; i++){
        printf(" %.2x", start[i]);
    }
    printf("\n");
}

void show_int(int x){
    show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x){
    show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void *x){
    show_bytes((byte_pointer)&x, sizeof(void *));
}

void show_strings(char *x){
    show_bytes((byte_pointer) x, strlen(x));
}

void test_show_bytes(int val){
    int ival = val;
    float fval = (float) ival;
    int *pval = &ival;
    show_int(ival);
    show_float(fval);
    show_pointer(pval);
}

int main(){
    test_show_bytes(12345);
    char *s = "abcdef";
    show_strings(s);
    return 0;
}

問題2.11

ポインタ演算によるin-placeスワップと、配列を逆順にするコード。 全くもって見事に「ARR01-C. 配列のサイズを求めるときに sizeof 演算子をポインタに適用しない」に違反したコードを書いていた。 sizeofで配列の長さを計算しているのに...というバグをつぶそうと30分ぐらい時間をつぶした。

www.jpcert.or.jp

#include <stdio.h>

void inplace_swap(int *x, int*y){
    *y = *x ^ *y;
    *x = *x ^ *y;
    *y = *x ^ *y;
}

void reverse_array(int a[], int cnt){
    int first, last;
    for (first = 0, last = cnt-1; first < last; first++, last--){
        inplace_swap(&a[first], &a[last]);
    }
}

void print_array(int a[], size_t len){
    int i;
    for (i=0; i < len; i++){
        printf("%d", a[i]);
    }
    printf("\n");

}

int main(){
    int b[5] = {1, 2, 3, 4, 5};
    print_array(b, sizeof(b) / sizeof(int));
    reverse_array(b, sizeof(b) / sizeof(int));
    print_array(b, sizeof(b) / sizeof(int));
    return 0;

}

問題2.13

VAXコンピュータには ANDとORの代わりにbisとbicという演算子があり…という件から始まる。

#include <stdio.h>

int bis(int x, int m){
    return x | m;
}

int bic(int x, int m){
    return x & (~m);
}

int bool_or(int x, int y){
    int result = bis(x, y);
    return result;
}

int bool_xor(int x, int y){
    int result = bis(bic(x, y), bic(y, x));
}

int main(){
    int x = 0x69;
    int y = 0x55;
    printf("%x | %x = %x\n", x, y, bool_or(x, y));
    printf("%x ^ %x = %x\n", x, y, bool_xor(x, y));
    return 0;

}

問題2.14

ビット演算と論理演算は別物ですよ、という問題

#include <stdio.h>

int main(){
    int x = 0x66;
    int y = 0x39;
    printf("%x & %x = %x\n", x, y, x & y);
    printf("%x | %x = %x\n", x, y, x | y);
    printf("~%x | ~%x = %x\n", x, y, ~x | ~y);
    printf("%x & !%x = %x\n", x, y, x & !y);
    printf("\n");
    printf("%x && %x = %x\n", x, y, x && y);
    printf("%x || %x = %x\n", x, y, x || y);
    printf("~%x || ~%x = %x\n", x, y, ~x || ~y);
    printf("%x && !%x = %x\n", x, y, x && !y);
}

『ゼロからはじめるデータサイエンス 第2版』の技術査読を担当しました

ゼロからデータサイエンスも第2版

2020年5月1日にオライリージャパンから『Data Science from Scratch, 2nd Edition』の邦訳である 『ゼロからはじめるデータサイエンス 第2版』が発売される。

www.oreilly.co.jp

この度、邦訳の査読者として参加させていただいた。 オライリーの方から話があったのは2020年2月であった。

読者は架空のソーシャルネットワーク運営企業、データサイエンス・スター社のデータサイエンティストという立場で社内に大量にいるヴァイスプレジデントたちの要求をデータサイエンスを駆使して、 ゼロから(便利なライブラリを使わずに)構築していく本である。

第2版とあるようにこの本は初版が原著2015年、邦訳2017年に出ている。 つまり、原著は5年分のアップデートを含んでいる。 また、初版ではPython 2とPython 3の過渡期であり、原著はPython 2対応で邦訳はPython 2/3対応という時代であったが、第2版では原著邦訳ともにPython 3対応である。 初版をお持ちの方は付録Aを見比べるとかなりすっきりしていることがわかると思う。

以前、台湾出身の同僚が原著を読んで勉強していたのを思い出した。 その同僚は非常に優秀で書いてあることなんてすでに知っているのではとすら思ったが、そのような姿勢が大事なのだろう。

ちょうとこのエントリを書いているのは大型連休の最中である。 本当はスプラトゥーンにうつつを抜かさずに勉強するべきだな...と思うのであるが。

書物の流通がどうなっているのかわからないが、ぜひ通販で購入するかオライリージャパンのサイトから電子書籍を購入して家で読んでいただきたい。

Pythonのタプルに括弧は必要なのかどうなのか

タプル同士の比較、そう思っていた時期が僕にもありました。

Pythonで式True, True, True == (True, True, True) がどう評価されるのか、頭の中で考えてみてほしい。 そして、お手元のPythonインタプリタTrue, True, True == (True, True, True) を実行してほしい。 どうなるだろうか。

>>> True, True, True == (True, True, True)
(True, True, False)

上記の結果は、あなたが予想していた結果だろうか。 それとも、全く違う結果だっただろうか。

なぜこうなるのか

ひとまず、なぜこうなるのか、Pythonの標準ライブラリの1つであるバイトコードアセンブラdisを使って、上記の式がどのようにバイトコードに変換されるのかを調べてみる。

>>> import dis
>>> dis.dis("True, True, True == (True, True, True)")
 1            0 LOAD_CONST               0 (True)
              2 LOAD_CONST               0 (True)
              4 LOAD_CONST               0 (True)
              6 LOAD_CONST               1 ((True, True, True))
              8 COMPARE_OP               2 (==)
             10 BUILD_TUPLE              3
             12 RETURN_VALUE

disのドキュメントに沿ってこのコードがどのような処理が行われるのかを追ってみる。

  1. スタックに Trueをプッシュする [True]
  2. スタックに Trueをプッシュする [True, True]
  3. スタックに Trueをプッシュする [True, True, True]
  4. スタックに (True, True, True)をプッシュする [True, True, True, (True, True, True)]
  5. スタックの先頭2つを等価比較して、結果をプッシュする
    1. True == (True, True, True) の結果はFalseである
    2. 結果のFalse をスタックにプッシュする [True, True, False]
  6. スタックから3つ取り出してタプルを作ってスタックにプッシュする [(True, True, False)]
  7. スタックの先頭を返り値として返す (True, True, False)

上記からわかることは、タプル同士の比較ではなく、左辺の一番右のTrueと右辺のタプルの比較が行われていることがわかる。 これは、そのようにパースされたからだと思われる。

実際、 6. 式 (expression) — Python 3.8.2 ドキュメント の「演算子の優先順位」を調べると、 Binding or parenthesized expressionよりも==の優先順序が上なので、タプル同士の比較ではなく上記のような動きになることがわかる。

どうすれば防げるのか

防ぐ方法の1つはmypyによるチェックである。

例えば、 hoge.pyとして、

b = True, True, True == (True, True, True)
print(b)

であるとき、

$ mypy --strict hoge.py

とすると、

hoge.py:1: error: Non-overlapping equality check (left operand type: "Literal[True]", right operand type: "Tuple[bool, bool, bool]")
Found 1 error in 1 file (checked 1 source file)

という結果が返ってくる。 --strictで指定される引数のうち、 --strict-equalityが利いていることがわかる。