トップ

プログラム実装事典

中間値

2 値の平均値を取る際 (a + b) / 2 とやってしまいがちだが、一般的な固定長符号付き整数で、a と b が任意の正の値を取りうるとすると、これはバグである。a + b がオーバフローした場合に正しくない結果となる。

オーバフローの注意は、任意の、平均値を取る処理で(あるいはもっと広く加算全般で)必要である。しかし、2 値の平均は特にプログラミングで 2 分探索などでしばしば現れるコードであり、ミスを入れ込みやすい。

2038 年問題のちょうど半分を過ぎた頃に顕在化したバグにもこれに類似したものがあったと思われる。

正しいコード例は a + (b - a) / 2 となるが、ただしこれでも固定長符号付き整数の全ての組合せに対して正しいわけではないので注意(たとえば 16 ビットで a = -30000, b = 30000 の場合を考えてみればわかる。逆に元のコードで、正しい結果となる例でもある)。

参考

可変長配列

サイズの増加を指数的(旧サイズを n とすると新サイズを r * n, r > 1 )にすべきわけ。また、r を黄金比未満にする理由。

http://www.kmonos.net/wlog/111.html

いけにえの技法

(「初めての人のための LISP」増補改訂版 p. 147)

たとえば、リストからある値の要素を破壊的に取り除き、結果のリストを返すルーチンを考える。

(delete-eqv! 1 '(1 1 2 3 3 1 1 4)) => (2 3 3 4)

このルーチンをそのまま書こうとすると、リストの先頭部分については、破壊的リスト操作の対象とするべき cdr がないので、特別扱いしないといけない。

そこで、まずリストの先頭にダミーの要素を追加して、特別扱いをしないルーチンを呼び、最後に cdr をとって戻す。このような、あとで捨てることを前提としてダミーを付けることで、処理を単純にする技法を「いけにえの技法」という。このような手法を特に指すような、一般的な英語の用語はないらしい。

  1 (define (delete-eqv! v lst)
  2   (define (f xxs xs)
  3     (if (pair? xs)
  4         (let ((s (cdr xs)))
  5              (cond ((eqv? (car xs) v)
  6                     (set-cdr! xxs s)
  7                     (f xxs s) )
  8                    (else
  9                     (f xs s) )))))
 10   (let ((lst2 (cons 'dummy lst)))
 11        (f lst2 lst)
 12        (cdr lst2) ))