表題

整数とビット

著者

Aubrey Jaffer

状態

この SRFI は現在「確定」の状態である。 SRFI の各状態の説明については ここ を参照せよ。 この SRFI に関する議論については メーリングリストのアーカイブ を参照せよ。

概要

整数を 2 の補数のビット列として捉える方法は難解であるが、 コンピュータ サイエンスの重要な領域である。 これは次のような用途に使われる。

論拠

この提案では、これは上記の目的に使われてきた、 SLIB モジュールの logical について述べる。

廃止された 「SRFI-33: 整数のビット演算ライブラリ」 に関する議論では、 手続きの名前や引数の個数に関して一貫性がないという欠点があった。 また、SRFI-47 のブール配列と競合している。

私は論理整数演算とブール配列の両方を実装してきたが、 それらの用途が競合するということはなかった。 私はブール配列を用いて、 何百万というレコードを持つデータベース テーブルの 非常に高速なインデックスを構築した。 RAM を使い切ってしまうことを避けるために、 100万ビットの配列を作成することが必要であった。 そのため、ブール配列の手続きは、その結果を引数に渡された配列に格納するようにした。 これに対して、この SRFI で述べる手続きは純粋に関数的である。

ビットと補数

この SRFI においては、ビット インデックスは 0 以上の整数であり、 最下位のビットのインデックスを 0 とする。 正の整数は有限個の "1" ビットを持つものとする。 負の整数は有限個の "0" ビットを持つものとする。

参照実装は Scheme の整数演算のみを用いて実装した。

補数 (complement) は負の整数を表現する方法である。 1 の補数の fixnum は、 -(2n) から 2n の範囲の整数であり、0 の表現は2通りある。 2 の補数の fixnum は、 -(2n+1) から 2n の範囲の整数である。

ここでは整数が 2 の補数の負数を持つと仮定しているので、 整数の 2 の補数は単にマイナスを付けたものになる。 整数の 1 の補数は lognot により計算できる。

(define (lognot n) (- -1 n))

ビット演算と整数のプロパティ

手続き logior, logxor, logand, lognot, logtest, logbit? (logbitp), ash, logcount, integer-length は Common-Lisp に由来する。 logior, logxor, logand は拡張されており、任意個の引数を受け取ることができる。 logtest に任意個の引数を渡すということはあまりないため、 拡張しなかった。

ビット演算」の節では、 直交で完全な関数群を定義するのではなく、 前述の応用例をサポートするのに十分なだけの、 最小限のビット論理関数を定義するようにした。

logior, logxor, logand のうちのいずれか2つがあれば、 (lognot を組み合わせることで) すべての2項論理関数を構成することはできるが、 この3つがあれば、 任意の自明でない2項論理関数は、 これらのうちの1つの2項論理関数と 0 回または 1 回の lognot の呼び出しで、合成することができる。

bitwise-if は SRFI-33 の bitwise-merge と同じものである。

SRFI-33 へのエイリアスである bitwise-ior, bitwise-xor, bitwise-and, bitwise-not, bitwise-merge, any-bits-set?, bit-count も提供する。

log2-binary-factors (first-set-bit のエイリアス) は単純で有用な関数であるが、その実装は分かりにくいだろう。

(define (log2-binary-factors n)
  (+ -1 (integer-length (logand n (- n)))))

ワード内のビットとビットのフィールド

ワード内のビット」と「ビットのフィールド」の手続きは、 デジタル論理のモデル化や、ソフトウェアのバイナリ データ構造のアクセスに使われる。

copy-bit-field の引数の順序は、 「ビットのフィールド」の他の手続きと一貫するように変更した。 startend インデックス引数は引数リストの最後に配置した。 この順序は substring や SRFI-47 の配列の引数の順序と同じである。

この startend インデックス引数は、 SRFI-33 の bit-field 手続きの最初に指定する size および position 引数とは互換性がない。

slib/logical.scm で定義されている手続き logical:rotate は、指定された個数の下位ビットを指定されたビット数だけローテートしていた。 この関数は非常に便利だが、適切な名前を付けることができなかった。 私はこの関数を rotate-bit-field に置き換え、 start 引数を追加した。 この新しい関数は、整数内の指定されたフィールド (start から end まで) をローテートし、それ以外のビットには何もしない。

もう1つ、名前が適切でない手続きが logical:ones である。 この手続きは、下位の k ビットが 1 である整数を生成する。 この関数の代わりに bit-field を用意した。 しかし、その定義は短すぎるので、私は次のような定義に置き換えた。

(lognot (ash -1 k))

bit-reverse 手続きは width 引数をとる唯一の手続きであった。 そこで、私はそれを reverse-bit-field に置き換えた。

LaminationGray-code に関する関数は slib/phil-spc.scm に移動した。

ブール値としてのビット

ブール値としてのビット」の節では、 整数ををブール値のリストに変換する手続きを定義する。 SRFI-33 にはこれに相当する機能はない。

仕様

ビット演算

Function: logand n1 ...
Function: bitwise-and n1 ...
整数のビットごとの AND をとった整数を返す。

例:

(number->string (logand #b1100 #b1010) 2)
    => "1000"

Function: logior n1 ...
Function: bitwise-ior n1 ...
整数のビットごとの OR をとった整数を返す。

例:

(number->string (logior #b1100 #b1010) 2)
    => "1110"

Function: logxor n1 ...
Function: bitwise-xor n1 ...
整数のビットごとの XOR をとった整数を返す。

例:

(number->string (logxor #b1100 #b1010) 2)
    => "110"

Function: lognot n
Function: bitwise-not n
整数の 1 の補数の整数を返す。

例:

(number->string (lognot #b10000000) 2)
    => "-10000001"
(number->string (lognot #b0) 2)
    => "-1"

Function: bitwise-if mask n0 n1
Function: bitwise-merge mask n0 n1
整数 n0 のいくつかのビットと、 整数 n1 のいくつかのビットで構成された整数を返す。 戻り値は、 整数 mask のビットが 1 であるビットは n0 のビットと同じであり、 mask のビットが 0 であるビットは n1 のビットと同じになる。

Function: logtest j k
Function: any-bits-set? j k
(logtest j k) == (not (zero? (logand j k)))

(logtest #b0100 #b1011) => #f
(logtest #b0100 #b0111) => #t

整数のプロパティ

Function: logcount n
Function: bit-count n
整数 n の中のビットの個数を返す。 整数が正なら、そのバイナリ表現における 1 のビットを数えて返す。 整数が負なら、その 2 の補数のバイナリ表現における 0 のビットを数えて返す。 整数が 0 なら、0 を返す。

例:

(logcount #b10101010)
    => 4
(logcount 0)
    => 0
(logcount -2)
    => 1

Function: integer-length n
整数 n を表現するのに必要なビット数を返す。

例:

(integer-length #b10101010)
    => 8
(integer-length 0)
    => 0
(integer-length #b1111)
    => 4

Function: log2-binary-factors n
Function: first-set-bit n
整数 n の素因数分解における 2 の個数を返す。 この値は、n における最下位の `1' ビットの ビット インデックスでもある。
(require 'printf)
(do ((idx 0 (+ 1 idx)))
      ((> idx 16))
    (printf "%s(%3d) ==> %-5d %s(%2d) ==> %-5d\n"
            'log2-binary-factors
            (- idx) (log2-binary-factors (- idx))
            'log2-binary-factors
            idx (log2-binary-factors idx)))
-|
log2-binary-factors(  0) ==> -1    log2-binary-factors( 0) ==> -1
log2-binary-factors( -1) ==> 0     log2-binary-factors( 1) ==> 0
log2-binary-factors( -2) ==> 1     log2-binary-factors( 2) ==> 1
log2-binary-factors( -3) ==> 0     log2-binary-factors( 3) ==> 0
log2-binary-factors( -4) ==> 2     log2-binary-factors( 4) ==> 2
log2-binary-factors( -5) ==> 0     log2-binary-factors( 5) ==> 0
log2-binary-factors( -6) ==> 1     log2-binary-factors( 6) ==> 1
log2-binary-factors( -7) ==> 0     log2-binary-factors( 7) ==> 0
log2-binary-factors( -8) ==> 3     log2-binary-factors( 8) ==> 3
log2-binary-factors( -9) ==> 0     log2-binary-factors( 9) ==> 0
log2-binary-factors(-10) ==> 1     log2-binary-factors(10) ==> 1
log2-binary-factors(-11) ==> 0     log2-binary-factors(11) ==> 0
log2-binary-factors(-12) ==> 2     log2-binary-factors(12) ==> 2
log2-binary-factors(-13) ==> 0     log2-binary-factors(13) ==> 0
log2-binary-factors(-14) ==> 1     log2-binary-factors(14) ==> 1
log2-binary-factors(-15) ==> 0     log2-binary-factors(15) ==> 0
log2-binary-factors(-16) ==> 4     log2-binary-factors(16) ==> 4

ワード内のビット

Function: logbit? index n
Function: bit-set? index n
(logbit? index n) == (logtest (expt 2 index) n)

(logbit? 0 #b1101) => #t
(logbit? 1 #b1101) => #f
(logbit? 2 #b1101) => #t
(logbit? 3 #b1101) => #t
(logbit? 4 #b1101) => #f

Function: copy-bit index from bit
index 番目のビット以外は from と同じビットを持つ整数を返す。 index 番目のビットは bit#t であれば 1 になり、 bit#f であれば 0 になる。

例:

(number->string (copy-bit 0 0 #t) 2)       => "1"
(number->string (copy-bit 2 0 #t) 2)       => "100"
(number->string (copy-bit 2 #b1111 #f) 2)  => "1011"

ビットのフィールド

Function: bit-field n start end
n における start (inclusive) から end (exclusive) までのビットで構成される整数を返す。 start 番目のビットが、戻り値の 0 番目のビットになる。

例:

(number->string (bit-field #b1101101010 0 4) 2)
    => "1010"
(number->string (bit-field #b1101101010 4 9) 2)
    => "10110"

Function: copy-bit-field to from start end
start (inclusive) から end (exclusive) までのビット以外は to と同じビットである整数を返す。 その範囲のビットは from と同じになる。 from の 0 番目のビットは、 戻り値の start 番目のビットになる。

例:

(number->string (copy-bit-field #b1101101010 0 0 4) 2)
    => "1101100000"
(number->string (copy-bit-field #b1101101010 -1 0 4) 2)
    => "1101101111"
(number->string (copy-bit-field #b110100100010000 -1 5 9) 2)
    => "110100111110000"

Function: ash n count
Function: arithmetic-shift n count
次の値に等しい整数を返す。
(inexact->exact (floor (* n (expt 2 count))))

例:

(number->string (ash #b1 3) 2)
    => "1000"
(number->string (ash #b1010 -1) 2)
    => "101"

Function: rotate-bit-field n count start end
整数 nstart から end までのビット フィールドを 上位の方向に count ビットだけ循環させた値を返す。

例:

(number->string (rotate-bit-field #b0100 3 0 4) 2)
    => "10"
(number->string (rotate-bit-field #b0100 -1 0 4) 2)
    => "10"
(number->string (rotate-bit-field #b110100100010000 -1 5 9) 2)
    => "110100010010000"
(number->string (rotate-bit-field #b110100100010000 1 5 9) 2)
    => "110100000110000"

Function: reverse-bit-field n start end
整数 nstart から end までのビットを逆順にした値を返す。
(number->string (reverse-bit-field #xa7 0 8) 16)
    => "e5"

ビットとブール値

Function: integer->list k len
Function: integer->list k
integer->listlen 個のブール値で構成されるリストを返す。 リストの各要素は整数の各ビットに対応し、 1 のビットは #t に、0 のビットは #f になる。 len 引数のデフォルトは (integer-length k) である。

Function: list->integer list
list->integer はブール値リスト list のビットで構成される整数を返す。 #t は 1 のビット、#f は 0 のビットになる。

integer->listlist->integerequal? で比較する限り、逆変換になる。

Function: booleans->integer bool1 ...
引数 bool1 ... をビット列に解釈した整数を返す。

実装

slib/logical.scm は、整数のビット列を操作する手続きを、 R4RS または R5RS に準拠する Scheme で実装している。

;;;; "logical.scm", bit access and operations for integers for Scheme
;;; Copyright (C) 1991, 1993, 2001, 2003, 2005 Aubrey Jaffer
;
;Permission to copy this software, to modify it, to redistribute it,
;to distribute modified versions, and to use it for any purpose is
;granted, subject to the following restrictions and understandings.
;
;1.  Any copy made of this software must include this copyright notice
;in full.
;
;2.  I have made no warranty or representation that the operation of
;this software will be error-free, and I am under no obligation to
;provide any services, by way of maintenance, update, or otherwise.
;
;3.  In conjunction with products arising from the use of this
;material, there shall be no use of my name in any advertising,
;promotional, or sales literature without prior written consent in
;each case.

(define logical:boole-xor
 '#(#(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)
    #(1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14)
    #(2 3 0 1 6 7 4 5 10 11 8 9 14 15 12 13)
    #(3 2 1 0 7 6 5 4 11 10 9 8 15 14 13 12)
    #(4 5 6 7 0 1 2 3 12 13 14 15 8 9 10 11)
    #(5 4 7 6 1 0 3 2 13 12 15 14 9 8 11 10)
    #(6 7 4 5 2 3 0 1 14 15 12 13 10 11 8 9)
    #(7 6 5 4 3 2 1 0 15 14 13 12 11 10 9 8)
    #(8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7)
    #(9 8 11 10 13 12 15 14 1 0 3 2 5 4 7 6)
    #(10 11 8 9 14 15 12 13 2 3 0 1 6 7 4 5)
    #(11 10 9 8 15 14 13 12 3 2 1 0 7 6 5 4)
    #(12 13 14 15 8 9 10 11 4 5 6 7 0 1 2 3)
    #(13 12 15 14 9 8 11 10 5 4 7 6 1 0 3 2)
    #(14 15 12 13 10 11 8 9 6 7 4 5 2 3 0 1)
    #(15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0)))

(define logical:boole-and
 '#(#(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)
    #(0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1)
    #(0 0 2 2 0 0 2 2 0 0 2 2 0 0 2 2)
    #(0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3)
    #(0 0 0 0 4 4 4 4 0 0 0 0 4 4 4 4)
    #(0 1 0 1 4 5 4 5 0 1 0 1 4 5 4 5)
    #(0 0 2 2 4 4 6 6 0 0 2 2 4 4 6 6)
    #(0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7)
    #(0 0 0 0 0 0 0 0 8 8 8 8 8 8 8 8)
    #(0 1 0 1 0 1 0 1 8 9 8 9 8 9 8 9)
    #(0 0 2 2 0 0 2 2 8 8 10 10 8 8 10 10)
    #(0 1 2 3 0 1 2 3 8 9 10 11 8 9 10 11)
    #(0 0 0 0 4 4 4 4 8 8 8 8 12 12 12 12)
    #(0 1 0 1 4 5 4 5 8 9 8 9 12 13 12 13)
    #(0 0 2 2 4 4 6 6 8 8 10 10 12 12 14 14)
    #(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)))

(define (logical:ash-4 x)
  (if (negative? x)
      (+ -1 (quotient (+ 1 x) 16))
      (quotient x 16)))

(define (logical:reduce op4 ident)
  (lambda args
    (do ((res ident (op4 res (car rgs) 1 0))
         (rgs args (cdr rgs)))
        ((null? rgs) res))))

;@
(define logand
  (letrec
      ((lgand
        (lambda (n2 n1 scl acc)
          (cond ((= n1 n2) (+ acc (* scl n1)))
                ((zero? n2) acc)
                ((zero? n1) acc)
                (else (lgand (logical:ash-4 n2)
                             (logical:ash-4 n1)
                             (* 16 scl)
                             (+ (* (vector-ref (vector-ref logical:boole-and
                                                           (modulo n1 16))
                                               (modulo n2 16))
                                   scl)
                                acc)))))))
    (logical:reduce lgand -1)))
;@
(define logior
  (letrec
      ((lgior
        (lambda (n2 n1 scl acc)
          (cond ((= n1 n2) (+ acc (* scl n1)))
                ((zero? n2) (+ acc (* scl n1)))
                ((zero? n1) (+ acc (* scl n2)))
                (else (lgior (logical:ash-4 n2)
                             (logical:ash-4 n1)
                             (* 16 scl)
                             (+ (* (- 15 (vector-ref
                                          (vector-ref logical:boole-and
                                                      (- 15 (modulo n1 16)))
                                          (- 15 (modulo n2 16))))
                                   scl)
                                acc)))))))
    (logical:reduce lgior 0)))
;@
(define logxor
  (letrec
      ((lgxor
        (lambda (n2 n1 scl acc)
          (cond ((= n1 n2) acc)
                ((zero? n2) (+ acc (* scl n1)))
                ((zero? n1) (+ acc (* scl n2)))
                (else (lgxor (logical:ash-4 n2)
                             (logical:ash-4 n1)
                             (* 16 scl)
                             (+ (* (vector-ref (vector-ref logical:boole-xor
                                                           (modulo n1 16))
                                               (modulo n2 16))
                                   scl)
                                acc)))))))
    (logical:reduce lgxor 0)))
;@
(define (lognot n) (- -1 n))
;@
(define (logtest n1 n2)
  (not (zero? (logand n1 n2))))
;@
(define (logbit? index n)
  (logtest (expt 2 index) n))
;@
(define (copy-bit index to bool)
  (if bool
      (logior to (arithmetic-shift 1 index))
      (logand to (lognot (arithmetic-shift 1 index)))))
;@
(define (bitwise-if mask n0 n1)
  (logior (logand mask n0)
          (logand (lognot mask) n1)))
;@
(define (bit-field n start end)
  (logand (lognot (ash -1 (- end start)))
          (arithmetic-shift n (- start))))
;@
(define (copy-bit-field to from start end)
  (bitwise-if (arithmetic-shift (lognot (ash -1 (- end start))) start)
              (arithmetic-shift from start)
              to))
;@
(define (rotate-bit-field n count start end)
  (define width (- end start))
  (set! count (modulo count width))
  (let ((mask (lognot (ash -1 width))))
    (define zn (logand mask (arithmetic-shift n (- start))))
    (logior (arithmetic-shift
             (logior (logand mask (arithmetic-shift zn count))
                     (arithmetic-shift zn (- count width)))
             start)
            (logand (lognot (ash mask start)) n))))
;@
(define (arithmetic-shift n count)
  (if (negative? count)
      (let ((k (expt 2 (- count))))
        (if (negative? n)
            (+ -1 (quotient (+ 1 n) k))
            (quotient n k)))
      (* (expt 2 count) n)))
;@
(define integer-length
  (letrec ((intlen (lambda (n tot)
                     (case n
                       ((0 -1) (+ 0 tot))
                       ((1 -2) (+ 1 tot))
                       ((2 3 -3 -4) (+ 2 tot))
                       ((4 5 6 7 -5 -6 -7 -8) (+ 3 tot))
                       (else (intlen (logical:ash-4 n) (+ 4 tot)))))))
    (lambda (n) (intlen n 0))))
;@
(define logcount
  (letrec ((logcnt (lambda (n tot)
                     (if (zero? n)
                         tot
                         (logcnt (quotient n 16)
                                 (+ (vector-ref
                                     '#(0 1 1 2 1 2 2 3 1 2 2 3 2 3 3 4)
                                     (modulo n 16))
                                    tot))))))
    (lambda (n)
      (cond ((negative? n) (logcnt (lognot n) 0))
            ((positive? n) (logcnt n 0))
            (else 0)))))
;@
(define (log2-binary-factors n)
  (+ -1 (integer-length (logand n (- n)))))

(define (bit-reverse k n)
  (do ((m (if (negative? n) (lognot n) n) (arithmetic-shift m -1))
       (k (+ -1 k) (+ -1 k))
       (rvs 0 (logior (arithmetic-shift rvs 1) (logand 1 m))))
      ((negative? k) (if (negative? n) (lognot rvs) rvs))))
;@
(define (reverse-bit-field n start end)
  (define width (- end start))
  (let ((mask (lognot (ash -1 width))))
    (define zn (logand mask (arithmetic-shift n (- start))))
    (logior (arithmetic-shift (bit-reverse width zn) start)
            (logand (lognot (ash mask start)) n))))
;@
(define (integer->list k . len)
  (if (null? len)
      (do ((k k (arithmetic-shift k -1))
           (lst '() (cons (odd? k) lst)))
          ((<= k 0) lst))
      (do ((idx (+ -1 (car len)) (+ -1 idx))
           (k k (arithmetic-shift k -1))
           (lst '() (cons (odd? k) lst)))
          ((negative? idx) lst))))
;@
(define (list->integer bools)
  (do ((bs bools (cdr bs))
       (acc 0 (+ acc acc (if (car bs) 1 0))))
      ((null? bs) acc)))
(define (booleans->integer . bools)
  (list->integer bools))

;;;;@ SRFI-60 aliases
(define ash arithmetic-shift)
(define bitwise-ior logior)
(define bitwise-xor logxor)
(define bitwise-and logand)
(define bitwise-not lognot)
(define bit-count logcount)
(define bit-set?   logbit?)
(define any-bits-set? logtest)
(define first-set-bit log2-binary-factors)
(define bitwise-merge bitwise-if)

;;; Legacy
;;(define (logical:rotate k count len) (rotate-bit-field k count 0 len))
;;(define (logical:ones deg) (lognot (ash -1 deg)))
;;(define integer-expt expt)            ; legacy name

著作権

Copyright (C) Aubrey Jaffer (2004, 2005). All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


編集者: David Van Horn
最終更新日時: Sun Jan 28 13:40:18 MET 2007