【Go言語】big.Int.SetBit()を使いこなす:実践的なコード例で学ぶ
func (z *Int) SetBit(x *Int, i int, b uint) *Int
この関数の引数と戻り値は以下の通りです。
- 戻り値
*Int
: 操作の結果が格納されたz
と同じbig.Int
ポインタが返されます。これにより、メソッドチェーンが可能になります。 b uint
: 設定するビットの値です。0
または1
のいずれかになります。b
が0
の場合、i
番目のビットはクリアされ(0になる)、b
が1
の場合、i
番目のビットはセットされます(1になる)。b
が0
または1
以外の場合、SetBit
はパニック(panic)を起こします。i int
: 設定したいビットの位置(インデックス)です。最下位ビット(LSB)が0番目のビット、その左隣が1番目のビット、というように数えられます。非負の整数である必要があります。x *Int
: ビット操作の元となるbig.Int
です。x
のコピーに対してビット操作が行われ、その結果がz
に格納されます。z
とx
が同じインスタンスであっても問題ありません。z *Int
: これはメソッドレシーバーであり、操作の結果が格納されるbig.Int
ポインタです。通常、このメソッドが呼ばれる対象のbig.Int
インスタンス自身が使われます。
SetBit
は基本的に以下の処理を行います。
x
の値をコピーします。- コピーした値の
i
番目のビットをb
の値(0または1)に設定します。 - 結果を
z
に格納し、z
を返します。
package main
import (
"fmt"
"math/big"
)
func main() {
// 元の数値 (10進数で 10, 2進数で 1010)
x := big.NewInt(10)
fmt.Printf("元の値 x: %s (2進数: %b)\n", x.String(), x) // 出力: 元の値 x: 10 (2進数: 1010)
// z を初期化
z := new(big.Int)
// 例1: 0番目のビットを1に設定 (1010 -> 1011)
// 元の x (10) の0番目のビットは0ですが、これを1にします。
// 結果は 11 (2進数で 1011)
z.SetBit(x, 0, 1)
fmt.Printf("0番目のビットを1に設定: %s (2進数: %b)\n", z.String(), z) // 出力: 0番目のビットを1に設定: 11 (2進数: 1011)
// 例2: 1番目のビットを0に設定 (1010 -> 1000)
// 元の x (10) の1番目のビットは1ですが、これを0にします。
// 結果は 8 (2進数で 1000)
z.SetBit(x, 1, 0)
fmt.Printf("1番目のビットを0に設定: %s (2進数: %b)\n", z.String(), z) // 出力: 1番目のビットを0に設定: 8 (2進数: 1000)
// 例3: 4番目のビットを1に設定 (1010 -> 11010)
// 元の x (10) には4番目のビットがありませんが、設定できます。
// 結果は 26 (2進数で 11010)
z.SetBit(x, 4, 1)
fmt.Printf("4番目のビットを1に設定: %s (2進数: %b)\n", z.String(), z) // 出力: 4番目のビットを1に設定: 26 (2進数: 11010)
// 注意: b が 0 または 1 以外の場合
// z.SetBit(x, 0, 2) // これはパニックを起こします
}
SetBit
関数自体が直接引き起こすエラーは比較的少ないですが、math/big
パッケージの利用全般にわたる誤解や、特定の引数の使い方による問題が発生することがあります。
b 引数に 0 または 1 以外の値を与えた場合のパニック
エラーの原因
SetBit(x *Int, i int, b uint)
の b
引数は、設定するビットの値(0 または 1)を示す uint
型です。この b
に 0
または 1
以外の値(例えば 2
や 3
など)を渡すと、SetBit
関数は実行時パニック (panic
) を引き起こします。
トラブルシューティング
- もし
b
が変数であれば、事前にif b != 0 && b != 1 { /* エラーハンドリング */ }
のようなチェックを行うことで、パニックを防ぐことができます。 b
に渡す値が常に0
または1
であることを確認してください。
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(10)
z := new(big.Int)
// これはOK
z.SetBit(x, 0, 1)
fmt.Println("OK:", z)
// これはパニックを引き起こす
// z.SetBit(x, 0, 2)
// fmt.Println("Panic:", z) // ここには到達しない
}
nil レシーバー (z) でメソッドを呼び出すことによるパニック
エラーの原因
Go のメソッド呼び出しにおいて、レシーバーが nil
のポインタである場合、メソッド内でレシーバーのメンバーにアクセスしようとするとパニック (nil pointer dereference
) が発生します。big.Int
のメソッドは通常ポインタレシーバー (*Int
) を取るため、初期化されていない *big.Int
変数に対して直接メソッドを呼び出すとこの問題に遭遇します。
トラブルシューティング
SetBit
を呼び出す前に、レシーバーとなるbig.Int
ポインタを必ず初期化してください。new(big.Int)
またはbig.NewInt(value)
を使用します。
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(10)
var z *big.Int // nil ポインタ
// エラー: runtime error: invalid memory address or nil pointer dereference
// z.SetBit(x, 0, 1)
// fmt.Println(z)
// 正しい初期化
z = new(big.Int) // または z := new(big.Int)
z.SetBit(x, 0, 1)
fmt.Println("初期化後:", z)
// あるいは、別の初期化方法
z2 := big.NewInt(0) // NewIntを使うと初期値0で初期化される
z2.SetBit(x, 0, 1)
fmt.Println("NewIntで初期化後:", z2)
}
x 引数に nil を渡すことによる論理エラー (まれ)
エラーの原因
SetBit(z *Int, x *Int, i int, b uint)
の x
引数(元の数値)が nil
であっても、Go の math/big
パッケージの内部実装では、nil
*big.Int
は数値の 0
として扱われることがあります。しかし、これは常に安全であるとは限らず、予期せぬ挙動を引き起こす可能性があります。特に、コードの意図が明確でなくなるため、避けるべきです。
トラブルシューティング
x
にも有効なbig.Int
インスタンスを渡すようにしてください。もしx
の値が0
であることを意図するならば、明示的にbig.NewInt(0)
を渡すべきです。
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
var xNil *big.Int // x が nil
z := new(big.Int)
// x が nil であってもパニックはしないが、意図が不明確になる可能性
// 結果は 0 の0番目のビットを1に設定するので 1
z.SetBit(xNil, 0, 1)
fmt.Printf("x が nil の場合: %s (2進数: %b)\n", z.String(), z) // 出力: x が nil の場合: 1 (2進数: 1)
// 意図が明確な書き方
xZero := big.NewInt(0)
z.SetBit(xZero, 0, 1)
fmt.Printf("x が big.NewInt(0) の場合: %s (2進数: %b)\n", z.String(), z) // 出力: x が big.NewInt(0) の場合: 1 (2進数: 1)
}
元の big.Int (x) が意図せず変更されると誤解する
誤解の原因
SetBit
のシグネチャは func (z *Int) SetBit(x *Int, i int, b uint) *Int
です。この構造から、z
が操作結果を格納する場所であり、x
は元の値として使われるだけである、と理解できます。しかし、math/big
の他の多くの関数(例: Add
, Mul
など)と同様に、SetBit
も z
と x
が同じインスタンスである場合を考慮して設計されています。つまり、x.SetBit(x, i, b)
のように呼び出すと、x
自身が変更されます。この動作は意図通りですが、他の関数と混同したり、注意が足りないと意図しない変更と誤解されることがあります。
トラブルシューティング
x
の元の値を保持したい場合は、必ず新しいbig.Int
インスタンスをz
として用意し、z.Set(x)
でx
の値をz
にコピーしてからz.SetBit
を呼び出すか、あるいはx
とは異なる別のbig.Int
インスタンスをz
に指定してください。
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
originalX := big.NewInt(10) // 元の 10 (1010)
fmt.Printf("元の originalX: %s (2進数: %b)\n", originalX.String(), originalX)
// ケース1: z に新しいインスタンスを使う(originalX は変更されない)
z1 := new(big.Int)
z1.SetBit(originalX, 0, 1) // originalX の 0ビット目を1に
fmt.Printf("z1 に新しいインスタンスを使用: z1=%s (2進数: %b), originalX=%s (2進数: %b)\n",
z1.String(), z1, originalX.String(), originalX)
// 出力: z1=11 (1011), originalX=10 (1010) - originalX は変更されない
// ケース2: レシーバー (z) と元 (x) に同じインスタンスを使う(originalX が変更される)
// これは big.Int の意図された動作であり、エラーではないが、注意が必要
originalX.SetBit(originalX, 1, 0) // originalX の 1ビット目を0に (1010 -> 1000)
fmt.Printf("originalX 自身を変更: originalX=%s (2進数: %b)\n", originalX.String(), originalX)
// 出力: originalX=8 (1000) - originalX が変更された
}
ビットインデックス i が負の数の場合
エラーの原因
ドキュメントには明示的に記載されていませんが、ビットインデックス i
は通常、非負の整数(0以上)を想定しています。負のインデックスを渡した場合の挙動は未定義または予期せぬ結果となる可能性があります。Go の math/big
パッケージの多くのビット操作関数は、負のビットインデックスに対してパニックを起こすか、0
を返したりすることがあります。
- ビットインデックス
i
が常に0
以上であることを確認してください。
big.Int.SetBit()
の基本
SetBit
のシグネチャは以下の通りです。
func (z *Int) SetBit(x *Int, i int, b uint) *Int
- 戻り値: 操作結果が格納された
z
と同じbig.Int
のポインタ。 b uint
: 設定するビットの値。0
または1
でなければなりません。それ以外の値だとパニックします。i int
: 設定したいビットの位置(インデックス)。最下位ビットが 0 です。x *Int
: ビット操作の元となるbig.Int
のポインタ。x
の値をコピーし、そのビットを変更します。z *Int
: 操作結果が格納されるbig.Int
のポインタ(レシーバー)。
いくつかの具体的なシナリオを通じて SetBit
の使い方を見ていきましょう。
例 1: 特定のビットを 1 に設定する(セットする)
ある整数の特定のビットを 1 に変更する最も基本的な例です。
package main
import (
"fmt"
"math/big"
)
func main() {
// 元の数値 (10進数で 10, 2進数で 1010)
x := big.NewInt(10)
fmt.Printf("元の値 x: %s (2進数: %b)\n", x.String(), x) // 出力: 元の値 x: 10 (2進数: 1010)
// 結果を格納する big.Int を初期化
z := new(big.Int)
// 0番目のビットを 1 に設定
// x の 0番目のビットは現在 0 です。これを 1 に変更します。
// 10 (1010) -> 11 (1011)
bitIndex := 0
bitValue := uint(1) // 1 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 0 番目のビットを 1 に設定: 11 (2進数: 1011)
// 3番目のビットを 1 に設定 (元々 1010 なので、3番目は 1 ですが、そのまま)
// 10 (1010) -> 10 (1010)
bitIndex = 3
bitValue = uint(1) // 1 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 3 番目のビットを 1 に設定: 10 (2進数: 1010)
// 4番目のビットを 1 に設定 (元の数には存在しない高い位置のビット)
// 10 (1010) -> 26 (11010)
bitIndex = 4
bitValue = uint(1) // 1 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 4 番目のビットを 1 に設定: 26 (2進数: 11010)
}
解説
z.SetBit(x, i, b)
は、x
の値を元にして i
番目のビットを b
に設定し、その結果を z
に格納します。x
自身は変更されません。存在しない高い位置のビットを指定した場合でも、big.Int
は自動的に必要なサイズまで拡張されます。
例 2: 特定のビットを 0 に設定する(クリアする)
特定のビットを 0 に変更する場合です。
package main
import (
"fmt"
"math/big"
)
func main() {
// 元の数値 (10進数で 13, 2進数で 1101)
x := big.NewInt(13)
fmt.Printf("元の値 x: %s (2進数: %b)\n", x.String(), x) // 出力: 元の値 x: 13 (2進数: 1101)
// 結果を格納する big.Int を初期化
z := new(big.Int)
// 0番目のビットを 0 に設定
// x の 0番目のビットは現在 1 です。これを 0 に変更します。
// 13 (1101) -> 12 (1100)
bitIndex := 0
bitValue := uint(0) // 0 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 0 番目のビットを 0 に設定: 12 (2進数: 1100)
// 2番目のビットを 0 に設定 (元々 13 (1101) の2番目は 1)
// 13 (1101) -> 9 (1001)
bitIndex = 2
bitValue = uint(0) // 0 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 2 番目のビットを 0 に設定: 9 (2進数: 1001)
// 4番目のビットを 0 に設定 (元の数には存在しない高い位置のビット、元々 0 なので変化なし)
// 13 (1101) -> 13 (1101)
bitIndex = 4
bitValue = uint(0) // 0 に設定
z.SetBit(x, bitIndex, bitValue)
fmt.Printf(" %d 番目のビットを %d に設定: %s (2進数: %b)\n", bitIndex, bitValue, z.String(), z)
// 出力: 4 番目のビットを 0 に設定: 13 (2進数: 1101)
}
解説
ビットを 0 に設定する場合も、同様に SetBit
を使用します。存在しないビットを 0 に設定しても、そのビットは元々 0 なので数値は変わりません。
例 3: レシーバー (z
) と元 (x
) が同じインスタンスの場合
SetBit
は z
と x
が同じ big.Int
インスタンスであっても正しく動作します。これは、x
のコピーに対して操作が行われ、結果が z
に格納されるためです。
package main
import (
"fmt"
"math/big"
)
func main() {
// 元の数値 (10進数で 10, 2進数で 1010)
val := big.NewInt(10)
fmt.Printf("元の値 val: %s (2進数: %b)\n", val.String(), val) // 出力: 元の値 val: 10 (2進数: 1010)
// val 自身の 0番目のビットを 1 に設定する
// 10 (1010) -> 11 (1011)
bitIndex := 0
bitValue := uint(1)
val.SetBit(val, bitIndex, bitValue) // レシーバーと元が同じ val
fmt.Printf("val 自身を更新後: %s (2進数: %b)\n", val.String(), val)
// 出力: val 自身を更新後: 11 (2進数: 1011)
// さらに val 自身の 2番目のビットを 0 に設定する
// 11 (1011) -> 9 (1001)
bitIndex = 2
bitValue = uint(0)
val.SetBit(val, bitIndex, bitValue) // レシーバーと元が同じ val
fmt.Printf("val をさらに更新後: %s (2進数: %b)\n", val.String(), val)
// 出力: val をさらに更新後: 9 (2進数: 1001)
}
解説
この方法は、元の数値を直接変更したい場合に便利です。val.SetBit(val, ...)
のように記述することで、val
が持つ値が更新されます。
例 4: 不正な b
引数によるパニック
SetBit
の b
引数には 0
または 1
のみ指定できます。それ以外の値を指定するとパニックが発生します。
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(10)
z := new(big.Int)
// これはパニックを引き起こす!
// b に 2 を指定しているため
// z.SetBit(x, 0, 2)
// fmt.Println(z) // ここには到達しない
fmt.Println("この行は実行されません(コメントアウトされた SetBit が有効な場合)")
}
解説
b
引数の値の検証は重要です。ユーザー入力など、信頼できないソースから値が来る場合は、if b != 0 && b != 1
のようなチェックを追加して、パニックを回避し、適切なエラーハンドリングを行うべきです。
例 5: nil
レシーバーによるパニック
big.Int
のポインタレシーバー (*big.Int
) は、メソッドを呼び出す前に初期化されている必要があります。
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(10)
var z *big.Int // 初期化されていない big.Int ポインタ (nil)
// これは runtime error: invalid memory address or nil pointer dereference を引き起こす!
// z.SetBit(x, 0, 1)
// fmt.Println(z) // ここには到達しない
// 正しい初期化方法
z = new(big.Int) // new() で big.Int のゼロ値(0)のポインタを生成
z.SetBit(x, 0, 1)
fmt.Println("正しく初期化された z:", z)
// あるいは big.NewInt で初期化
z2 := big.NewInt(0) // 0 に初期化
z2.SetBit(x, 0, 1)
fmt.Println("big.NewInt で初期化された z2:", z2)
}
解説
var z *big.Int
だけでは z
は nil
です。nil
ポインタに対してメソッドを呼び出すと、内部で nil
の指すメモリアドレスにアクセスしようとしてパニックします。new(big.Int)
または big.NewInt(...)
で明示的に初期化する必要があります。
主な代替方法は、ビットマスク(Bitmask) を使用した論理演算です。math/big
パッケージには、これらの操作をサポートする関数が提供されています。
big.Int.SetBit()
の代替方法
ビットマスクとビットごとの OR (Or) を使用してビットをセットする(1にする)
特定のビットを 1 に設定したい場合、そのビット位置にのみ 1 が立つビットマスクを作成し、元の数値とビットごとの OR 演算を行います。
- 代替操作
z.Or(x, big.NewInt(1).Lsh(big.NewInt(1), uint(i)))
- 元の操作
z.SetBit(x, i, 1)
解説
big.NewInt(1)
: 1 を表すbig.Int
を作成します(これは2進数で...001
)。.Lsh(big.NewInt(1), uint(i))
: このbig.Int
をi
ビット左にシフトします。これにより、i
番目のビットのみが 1 であるようなビットマスクが作成されます(例:i=3
なら00001000
)。z.Or(x, mask)
: 元の数値x
と作成したマスクに対してビットごとの OR 演算を行います。これにより、マスクで 1 になっている位置のビットは強制的に 1 になり、それ以外のビットはx
の元の値が保持されます。
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(10) // 2進数: 1010
fmt.Printf("元の値 x: %s (2進数: %b)\n", x.String(), x)
// SetBit を使用する場合 (基準)
zSetBit := new(big.Int)
zSetBit.SetBit(x, 0, 1) // 0番目のビットを1に設定 (1010 -> 1011)
fmt.Printf("SetBit で 0番目を1に: %s (2進数: %b)\n", zSetBit.String(), zSetBit)
// 代替方法: ビットマスクと Or を使用
zOr := new(big.Int)
bitIndex := 0
// 1をbitIndex分だけ左シフトしてマスクを作成 (0番目なら 1)
mask := new(big.Int).Lsh(big.NewInt(1), uint(bitIndex))
zOr.Or(x, mask) // x とマスクのビットごとのOR
fmt.Printf("Or とマスクで 0番目を1に: %s (2進数: %b)\n", zOr.String(), zOr)
// 4番目のビットを1に設定 (1010 -> 11010)
zSetBit.SetBit(x, 4, 1)
fmt.Printf("SetBit で 4番目を1に: %s (2進数: %b)\n", zSetBit.String(), zSetBit)
zOr2 := new(big.Int)
bitIndex = 4
mask = new(big.Int).Lsh(big.NewInt(1), uint(bitIndex))
zOr2.Or(x, mask)
fmt.Printf("Or とマスクで 4番目を1に: %s (2進数: %b)\n", zOr2.String(), zOr2)
}
ビットマスクとビットごとの AND (And)、AND NOT (AndNot) を使用してビットをクリアする(0にする)
特定のビットを 0 に設定したい場合、そのビット位置にのみ 0 が立ち、他がすべて 1 であるビットマスクを作成し、元の数値とビットごとの AND 演算を行います。あるいは、対象ビットに 1 が立つマスクを作成し、AndNot
演算を使用します。
-
代替操作 (AndNot)
z.AndNot(x, big.NewInt(1).Lsh(big.NewInt(1), uint(i)))
解説
big.NewInt(1).Lsh(big.NewInt(1), uint(i))
:i
番目のビットのみが 1 であるビットマスクM
を作成します。z.AndNot(x, M)
: これはx AND (NOT M)
と等価です。つまり、M
で 1 になっているビット位置のx
のビットを 0 にクリアし、他のビットはx
の値を保持します。これはビットクリアの目的で非常に直接的です。
-
代替操作 (AND とビットマスク反転)
z.And(x, big.NewInt(0).Not(big.NewInt(1).Lsh(big.NewInt(1), uint(i))))
解説
big.NewInt(1).Lsh(big.NewInt(1), uint(i))
:i
番目のビットのみが 1 であるビットマスクM
を作成します(例:i=2
なら00000100
)。big.NewInt(0).Not(M)
: マスクM
のビットを反転させます。これにより、i
番目のビットのみが 0 で、他がすべて 1 であるマスク~M
が作成されます(例:i=2
なら...11111011
)。z.And(x, ~M)
: 元の数値x
と反転させたマスク~M
に対してビットごとの AND 演算を行います。これにより、マスクで 0 になっている位置のビットは強制的に 0 になり、それ以外のビットはx
の元の値が保持されます。
-
元の操作
z.SetBit(x, i, 0)
コード例
package main
import (
"fmt"
"math/big"
)
func main() {
x := big.NewInt(13) // 2進数: 1101
fmt.Printf("元の値 x: %s (2進数: %b)\n", x.String(), x)
// SetBit を使用する場合 (基準)
zSetBit := new(big.Int)
zSetBit.SetBit(x, 0, 0) // 0番目のビットを0に設定 (1101 -> 1100)
fmt.Printf("SetBit で 0番目を0に: %s (2進数: %b)\n", zSetBit.String(), zSetBit)
// 代替方法1: And とビットマスク反転を使用
zAndNotMask := new(big.Int)
bitIndex := 0
// 1をbitIndex分だけ左シフトしてマスクを作成 (0番目なら 1)
mask := new(big.Int).Lsh(big.NewInt(1), uint(bitIndex))
// マスクを反転
notMask := new(big.Int).Not(mask) // Not は引数のビットを反転
zAndNotMask.And(x, notMask) // x と反転マスクのビットごとのAND
fmt.Printf("And と反転マスクで 0番目を0に: %s (2進数: %b)\n", zAndNotMask.String(), zAndNotMask)
// 代替方法2: AndNot を使用 (より直接的)
zAndNot := new(big.Int)
bitIndex = 0
mask = new(big.Int).Lsh(big.NewInt(1), uint(bitIndex))
zAndNot.AndNot(x, mask) // x AND (NOT mask)
fmt.Printf("AndNot で 0番目を0に: %s (2進数: %b)\n", zAndNot.String(), zAndNot)
// 2番目のビットを0に設定 (1101 -> 1001)
zSetBit.SetBit(x, 2, 0)
fmt.Printf("SetBit で 2番目を0に: %s (2進数: %b)\n", zSetBit.String(), zSetBit)
zAndNot2 := new(big.Int)
bitIndex = 2
mask = new(big.Int).Lsh(big.NewInt(1), uint(bitIndex))
zAndNot2.AndNot(x, mask)
fmt.Printf("AndNot で 2番目を0に: %s (2進数: %b)\n", zAndNot2.String(), zAndNot2)
}
-
ビットマスクと論理演算 (
Or
,And
,AndNot
):- 利点
- 複数のビット操作を連続して行う場合に、より柔軟な表現が可能。
- 例えば、複数のビットを一括でセットしたりクリアしたりするビットマスクを事前に作成しておき、それを使い回すことができる。
b
引数のバリデーションに関するパニックの心配がない(ただし、マスクの生成や論理演算のロジックが間違っていると結果が狂う)。
- 欠点
- 単一ビットの操作には冗長になる可能性がある。
- ビットマスクの作成ロジックが複雑になると、可読性が低下する可能性がある。
- 推奨
より複雑なビット操作のパターンを実装する場合や、SetBit
のような直接的な関数では表現しにくいカスタムのビット操作ロジックが必要な場合に検討します。
- 利点
-
big.Int.SetBit()
:- 利点
最も直接的で意図が明確。読みやすく、簡潔。 - 欠点
0
または1
以外のb
引数を与えるとパニックする。 - 推奨
特定のビットを単独で設定/クリアする場合には、この関数が最も推奨されます。
- 利点