今回は F# のビット操作について説明します。
F# の場合、同じ記号を 3 つ続けたものがビット演算子になります。F# のビット演算子を下表に示します。
演算子 | 操作 |
---|---|
x &&& y | ビットごとの論理積 (AND) |
x ||| y | ビットごとの論理和 (OR) |
x ^^^ y | ビットごとの排他的論理和 (XOR) |
~~~x | ビットごとの否定 (NOT) |
x <<< y | x を y ビット左シフト |
x >>> y | x を y ビット右シフト |
演算子 &&& はビットごとの論理積を返します。
5 &&& 3 => 1
0101 AND 0011 --------- 0001
演算子 ||| はビットごとの論理和を返します。
5 ||| 3 => 7
0101 OR 0011 -------- 0111
演算子 ^^^ はビットごとの排他的論理和を返します。
5 ^^^ 3 => 6
0101 XOR 0011 --------- 0110
演算子 ~~~ はビットごとの論理的な否定を返します。
~~~1 => -2 ~~~0 => -1
<<<, >>> はビットをシフトする演算子です。左シフトの場合、下位ビットには 0 が挿入されます。右シフトの場合、正の整数 (または無符号整数) では上位ビットに 0 が挿入されます。負の整数では 1 が挿入されます。これを「算術シフト」といいます。
1 <<< 8 => 256 1 <<< 16 => 65536 256 >>> 8 => 1 65536 >>> 8 => 256 -256 >>> 8 => -1
それでは簡単な例題として、基本的なビット操作関数を作ってみましょう。
> let testBit x n = x &&& (1 <<< n) <> 0;; val testBit: x: int -> n: int32 -> bool > let setBit x n = x ||| (1 <<< n);; val setBit: x: int -> n: int32 -> int > let clearBit x n = x &&& ~~~(1 <<< n);; val clearBit: x: int -> n: int32 -> int
testBit は整数 x の n 番目のビットが 1 ならば true を返します。最下位 (LSB) のビットが 0 番目になります。int32 (32 bit) の場合、n は 0 から 31 になります。1 を n ビット左シフトして、x との論理積が 0 でなければ、n 番目のビットは 1 であることがわかります。
setBit は x の n 番目のビットを 1 にセットします。1 を n ビット左シフトして、x との論理和を計算すれば、n 番目のビットを 1 にすることができます。clearBit は x の n 番目のビットを 0 にクリアします。これは n 番目以外のビットを 1 に、n 番目のビットを 0 にして、それと x の論理積を計算すれば、n 番目のビットをクリアすることができます。1 を n ビット左シフトして、その否定を計算すると、n 番目のビット以外は 1 になります。
それでは実際に試してみましょう。
> testBit 256 7;; val it: bool = false > testBit 256 8;; val it: bool = true > testBit 256 9;; val it: bool = false > for i = 0 to 7 do - let x = setBit 0 i - printfn "%d" x - clearBit x i |> printfn "%d" ;; 1 0 2 0 4 0 8 0 16 0 32 0 64 0 128 0 val it: unit = ()
組み合わせの生成は拙作のページ 順列と組み合わせ で取り上げました。このほかに、n 個の中から m 個を選ぶ組み合わせは、ビットの 0, 1 で表すことができます。たとえば、5 個の数字 (0 - 4) から 3 個を選ぶ場合、数字を 0 番目 から 4 番目のビットに対応させます。すると、1, 3, 4 という組み合わせは 11010 と表すことができます。簡単な例題として、ビットを使って組み合わせを求めてみましょう。
組み合わせを求めるプログラムは次のようになります。
リスト : 組み合わせの生成 let combinations func n m = let rec comb n m a = if m = 0 then func a else if m = n then func (a ||| ((1 <<< m) - 1)) else ( comb (n - 1) m a comb (n - 1) (m - 1) (a ||| (1 <<< (n - 1))) ) comb n m 0
関数 combinations は n 個の中から m 個を選ぶ組み合わせを生成して、引数の関数 f に渡します。実際の処理は局所関数 comb で行います。組み合わせは引数 a にセットします。m が 0 になったら、組み合わせがひとつできたので func a を呼び出します。n が m と等しくなったならば、残り m 個を全て選びます。(1 <<< m) - 1 で m 個のビットをオンにして関数 f を呼び出します。
あとは comb を再帰呼び出しします。最初の呼び出しは n 番目の数字を選ばない場合です。n - 1 個の中から m 個を選びます。次の呼び出しが n 番目の数字を選ぶ場合で、a の n - 1 番目のビットをオンにします。そして、n - 1 個の中から m - 1 個を選びます。
それでは 5 個の中から 3 個を選ぶ組み合わせの実行例を示します。
> let combinations func n m = - let rec comb n m a = - if m = 0 then func a - else if m = n then func (a ||| ((1 <<< m) - 1)) - else ( - comb (n - 1) m a - comb (n - 1) (m - 1) (a ||| (1 <<< (n - 1))) - ) - comb n m 0;; val combinations: func: (int -> unit) -> n: int -> m: int -> unit > combinations (fun x -> printfn "%05B" x) 5 3;; 00111 01011 01101 01110 10011 10101 10110 11001 11010 11100 val it: unit = ()
この場合、最小値は 111 (0x07)1) で最大値は 11100 (0x1c) になります。このように、combinations は組み合わせを表す数を昇順で出力します。
最も右側 (LSB 側) にある 1 を 0 にクリアする、逆に最も右側にある 0 を 1 にセットすることは簡単にできます。
(1) 右側にある 1 をクリア => x &&& (- x) x : 1 1 1 1 x - 1 : 1 1 1 0 ---------------- AND : 1 1 1 0 x : 1 0 0 0 x - 1 : 0 1 1 1 ---------------- AND : 0 0 0 0 (2) 右側にある 0 を 1 にセット => x ||| (x + 1) x : 0 0 0 0 x + 1 : 0 0 0 1 ---------------- OR : 0 0 0 1 x : 0 1 1 1 x - 1 : 1 0 0 0 ---------------- OR : 1 1 1 1
上図 (1) を見てください。x から 1 を引くと、右側から連続している 0 は桁借りにより 1 になり、最初に出現する 1 が 0 になります。したがって、x &&& (x - 1) を計算すると、最も右側にある 1 を 0 にクリアすることができます。(2) の場合、x に 1 を足すと、右側から連続している 1 は桁上がりにより 0 になり、最初に出現する 0 が 1 になります。x ||| (x + 1) を計算すれば、最も右側にある 0 を 1 にセットすることができます。
また、最も右側にある 1 を取り出すことも簡単にできます。簡単な例として 4 ビットの整数値を考えてみます。負の整数を 2 の補数で表した場合、4 ビットで表される整数は -8 から 7 になります。次の図を見てください。
0 : 0000 1 : 0001 -1 : 1111 1 &&& (-1) => 0001 2 : 0010 -2 : 1110 2 &&& (-2) => 0010 3 : 0011 -3 : 1101 3 &&& (-3) => 0001 4 : 0100 -4 : 1100 4 &&& (-4) => 0100 5 : 0101 -5 : 1011 5 &&& (-5) => 0001 6 : 0110 -6 : 1010 6 &&& (-6) => 0010 7 : 0111 -7 : 1001 7 &&& (-7) => 0001 -8 : 1000 図 : 最も右側にある 1 を取り出す方法
2 の補数はビットを反転した値 (1 の補数) に 1 を加算することで求めることができます。したがって、x と -x の論理積 x &&& (-x) は、最も右側にある 1 だけが残り、あとのビットはすべて 0 になります。
次は、ビットが 1 の個数を数える処理を作ってみましょう。プログラムは次のようになります。
リスト : ビットカウント let bitCount n = let rec iter m c = if m = 0 then c else iter (m &&& (m - 1)) (c + 1) iter n 0
整数 n の右側から順番に 1 をクリアしていき、0 になるまでの回数を求めます。とても簡単ですね。32 個のビットを順番に調べるよりも高速です。
int を 32 bit とする場合、次の方法で 1 の個数をもっと高速に求めることができます。
リスト : ビットカウント (2) let bitCount1 n = let a = (n &&& 0x55555555) + ((n >>> 1) &&& 0x55555555) let b = (a &&& 0x33333333) + ((a >>> 2) &&& 0x33333333) let c = (b &&& 0x0f0f0f0f) + ((b >>> 4) &&& 0x0f0f0f0f) let d = (c &&& 0x00ff00ff) + ((c >>> 8) &&& 0x00ff00ff) (d &&& 0xffff) + (d >>> 16)
最初に、整数を 2 bit ずつに分割して、1 の個数を求めます。たとえば、整数 n を 4 bit で考えてみましょう。5 を 2 進数で表すと 0101 になり、n と論理積を計算すると 0, 2 番目のビットが 1 であれば、結果の 0, 2 番目のビットは 1 になります。同様に n を 1 ビット右シフトして論理積を計算すると、1, 3 番目のビットが 1 であれば、結果の 0, 2 番目のビットは 1 になります。あとは、それを足し算すれば 2 bit の中にある 1 の個数を求めることができます。
変数 a には 2 ビットの中の 1 の個数が格納されています。左隣の 2 ビットの値を足し算すれば、4 ビットの中の 1 の個数を求めることができます。次に、左隣の 4 ビットの値を足し算して 8 ビットの中の 1 の個数を求め、左隣の 8 ビットの値を足し算して、というように順番に値を加算していくと 32 ビットの中にある 1 の個数を求めることができます。
bitCount は 1 の個数が多くなると遅くなりますが、bitCount1 は 1 の個数に関係なく高速に動作します。興味のある方は試してみてください。
簡単な実行例を示します。
> let bitCount n = - let rec iter m c = - if m = 0 then c - else iter (m &mmap;&& (m - 1)) (c + 1) - iter n 0;; val bitCount: n: int -> int > let bitCount1 n = - let a = (n &mmap;&& 0x55555555) + ((n >>> 1) &mmap;&& 0x55555555) - let b = (a &mmap;&& 0x33333333) + ((a >>> 2) &mmap;&& 0x33333333) - let c = (b &mmap;&& 0x0f0f0f0f) + ((b >>> 4) &mmap;&& 0x0f0f0f0f) - let d = (c &mmap;&& 0x00ff00ff) + ((c >>> 8) &mmap;&& 0x00ff00ff) - (d &mmap;&& 0xffff) + (d >>> 16);; val bitCount1: n: int -> int > bitCount 0;; val it: int = 0 > bitCount1 0;; val it: int = 0 > bitCount 256;; val it: int = 1 > bitCount1 256;; val it: int = 1 > bitCount 65535;; val it: int = 16 > bitCount1 65535;; val it: int = 16 > bitCount 0x7fffffff;; val it: int = 31 > bitCount1 0x7fffffff;; val it: int = 31 > bitCount 0x7f00ff00;; val it: int = 15 > bitCount1 0x7f00ff00;; val it: int = 15
それでは、例題として Puzzel DE Programming で取り上げた ライツアウト というパズルを F# で解いてみましょう。ライツアウトは光っているボタンをすべて消すことが目的のパズルです。ルールはとても簡単です。あるボタンを押すと、そのボタンと上下左右のボタンの状態が反転します。つまり、光っているボタンは消灯し消えていたボタンは点灯します。次の図を見てください。
図 : ライツアウトの点灯パターン
ボタンは 5 行 5 列に配置されています。上図のように、中央のボタン 12 を押すとそのボタンと上下左右のボタンの状態が反転します。ライツアウトはライトオン・オフの 2 種類の状態しかないので、盤面はリストよりもビットを使って表した方が簡単です。ライトオン・オフの状態を 1 と 0 で表し、各ビットとボタンの座標を対応させると、盤面は 0 から 33554431 の整数値で表すことができます。
ボタンを押してライトの状態を反転する処理も簡単です。たとえば、中央のボタン 12 を押した場合、7, 11, 12, 13, 17 のライトを反転させます。この場合、5 つのボタンのビットをオンにした値 0x23880 と、盤面を表す整数値の排他的論理和 (XOR) を求めれば、5 つのライトの状態を反転することができます。次の例を見てください。
0 XOR 0x23880 => 0x23880 % 消灯の状態でボタン 12 を押す(点灯する) #x23880 XOR 0x23880 => 0 % もう一度同じボタンを押す(消灯する)
このように、ライツアウトは同じボタンを二度押すと元の状態に戻ります。したがって、同じボタンは二度押さなくてよいことがわかります。また、実際にボタンを押してみるとわかりますが、ボタンを押す順番は関係がないことがわかります。たとえば、ボタン 0 と 1 を押す場合、0 -> 1 と押すのも 1 -> 0 と押すのも同じ結果になります。
この 2 つの法則から、ボタンを押す組み合わせは全部で 225 通りになります。ライツアウトを解くいちばん単純な方法は、ボタンを押す組み合わせを生成して、実際にライトが全部消えるかチェックすることです。最近のパソコンは高性能なので、単純な方法でも解くことができます。
プログラムは次のようになります。
リスト : ライツアウトの解法 (lo.fsx) // ボタンを押したときの反転パターン let pattern = [| 0x0000023; 0x0000047; 0x000008e; 0x000011c; 0x0000218; 0x0000461; 0x00008e2; 0x00011c4; 0x0002388; 0x0004310; 0x0008c20; 0x0011c40; 0x0023880; 0x0047100; 0x0086200; 0x0118400; 0x0238800; 0x0471000; 0x08e2000; 0x10c4000; 0x0308000; 0x0710000; 0x0e20000; 0x1c40000; 0x1880000 |] // 組み合わせの生成 let combinations func n m = let rec comb n m a = if m = 0 then func a else if m = n then func (a ||| ((1 <<< m) - 1)) else ( comb (n - 1) m a comb (n - 1) (m - 1) (a ||| (1 <<< (n - 1))) ) comb n m 0 // 解答の表示 let printAnswer x = for i = 0 to 24 do printf (if x &&& (1 <<< i) <> 0 then "O " else ". ") if (i + 1) % 5 = 0 then printfn "" printfn "" let check board x = let mutable b = board for i = 0 to 24 do if x &&& (1 <<< i) <> 0 then b <- b ^^^ pattern.[i] if b = 0 then printAnswer x let lo board = for i = 1 to 25 do printfn "----- %d -----" i combinations (fun x -> check board x) 25 i
関数 lo で関数 comnbinations を呼び出して、25 個の中から i 個のボタンを押す組み合わせを生成します。関数 check の引数 board が盤面を表し、引数 x が押すボタンの位置を表します。今回は board の値を書き換えたいので、mutable な変数 b を用意します。
ボタンはビットの位置で表されているので、for ループでビットが 1 の位置を調べます。あとは b と pattern.[i] の排他的論理和を求めれば、そのボタンを押したことになります。選んだボタンを全部押して b が 0 になれば関数 printAnswer で解を表示します。
あとは特に難しいところはないと思います。それでは実行してみましょう。ライトが全部点灯している状態 (0x1ffffff) を解いてみます。
> open Lo;; > lo 0x1ffffff;; ----- 1 ----- ----- 2 ----- ----- 3 ----- ----- 4 ----- ----- 5 ----- ----- 6 ----- ----- 7 ----- ----- 8 ----- ----- 9 ----- ----- 10 ----- ----- 11 ----- ----- 12 ----- ----- 13 ----- ----- 14 ----- ----- 15 ----- . O O . O . O O O . . . O O O O O . O O O O . . . . . . O O O O . O O O O O . . . O O O . O . O O . O O . . . O O . O O . . O O O . O O O . . O O . O O . O O . . O O O . O O O . . O O . O O . . . O O ----- 16 ----- ----- 17 ----- ----- 18 ----- ----- 19 ----- ----- 20 ----- ----- 21 ----- ----- 22 ----- ----- 23 ----- ----- 24 ----- ----- 25 ----- val it: unit = ()
4 通りの解が出力されました。ボタンが全部点灯している場合、ボタンを押す回数はどの解も 15 回になります。実は、これがライツアウトの最長手数なのです。
ライトの点灯パターンは 225 = 33554432 通りありますが、実際に解が存在するパターンは、その 1 / 4 の 8388608 通りしかありません。その中で最短回数が 15 回で解けるパターンは 7350 通りあり、そのうちのひとつがライトが全部点灯しているパターンなのです。ライツアウトの最長手数に興味のある方は、Puzzle DE Programming:ライツアウト の 最長手数を求める をお読みくださいませ。
ライツアウトは次の図に示すように、ボタンを上から 1 行ずつ消灯していくという、わかりやすい方法で解くことができます。
図 : 1 行ずつボタンを消灯していく方法
(1) では、1 行目のボタンが 2 つ点灯しています。このボタンを消すには、真下にある 2 行目の B と D のボタンを押せばいいですね。すると (2) の状態になります。次に、2 行目のボタンを消します。3 行目の A, B, D, E のボタンを押して (3) の状態になります。
あとはこれを繰り返して 4 行目までのボタンを消したときに、5 行目のボタンも全部消えていれば成功となります。(4) のように、5 行目のボタンが消えない場合は失敗です。この場合は、1 行目のボタンを押して、点灯パターンを変更します。
2 - 5 行目のボタンの押し方は、1 行目の点灯パターンにより決定されるので、けっきょく 1 行目のボタンの押し方により、解けるか否かが決まります。この場合、ボタンの押し方は、25 = 32 通りしかありせん。つまり、たった 32 通り調べるだけでライツアウトの解を求めることができるのです。
プログラムは次のようになります。
リスト : ライツアウトの高速化 let lo1 board = for i = 0 to 31 do let mutable b = board let mutable p = 0 // 1 行目を押す for j = 0 to 4 do if i &&& (1 <<< j) <> 0 then ( b <- b ^^^ pattern.[j] p <- p ||| (1 <<< j) ) // 2 - 5 行目を消す for j = 0 to 19 do if b &&& (1 <<< j) <> 0 then ( b <- b ^^^ pattern.[j + 5] p <- p ||| (1 <<< (j + 5)) ) if b = 0 then printAnswer p
1 行目のボタンの押し方は 32 通りあるので、ボタンの押し方を 0 から 31 までの数値で表すことにします。値は 5 ビットで表すことができるので、ビットとボタンの位置を対応させて、ビットがオンであればそのボタンを押すことにします。盤面 board を mutable な変数 b にセットして、演算子 ^^^ で b の点灯パターンを変更します。押したボタンの位置は mutable な変数 p にセットします。
次は、1 行ずつ盤面 b のライトを消していきます。変数 j がチェックするボタンの位置を表します。b &&& (1 <<< j) で j の位置のビットを調べ、それがオンであればライトが点灯しいるので 1 行下のボタンを押します。押すボタンの位置は j + 5 で求めることができます。そして最後に盤面 b の値をチェックします。b が 0 であればライトが全部消えています。関数 printAnswer で解を出力します。
実行結果は同じなので省略させていただきます。興味のある方は試してみてください。なお、下記 URL によると、ライツアウトの解法は連立一次方程式を解くことに帰着させることができるそうです。
拙作のページ お気楽 Numpy プログラミング超入門 や Julia Programming: Puzzle DE Julia!! でも連立方程式によるライツアウトの解法を取り上げています。よろしければお読みくださいませ。
ビットが 1 の個数を数える方法は フィンローダさん の 初級C言語Q&A(15) を参考にさせていただきました。フィンローダさんに感謝いたします。
// // lo.fsx : ライツアウトの解法 // // Copyright (C) 2022 Makoto Hiroi // // ボタンを押したときの反転パターン let pattern = [| 0x0000023; 0x0000047; 0x000008e; 0x000011c; 0x0000218; 0x0000461; 0x00008e2; 0x00011c4; 0x0002388; 0x0004310; 0x0008c20; 0x0011c40; 0x0023880; 0x0047100; 0x0086200; 0x0118400; 0x0238800; 0x0471000; 0x08e2000; 0x10c4000; 0x0308000; 0x0710000; 0x0e20000; 0x1c40000; 0x1880000 |] // 組み合わせの生成 let combinations func n m = let rec comb n m a = if m = 0 then func a else if m = n then func (a ||| ((1 <<< m) - 1)) else ( comb (n - 1) m a comb (n - 1) (m - 1) (a ||| (1 <<< (n - 1))) ) comb n m 0 // 解答の表示 let printAnswer x = for i = 0 to 24 do printf (if x &&& (1 <<< i) <> 0 then "O " else ". ") if (i + 1) % 5 = 0 then printfn "" printfn "" let check board x = let mutable b = board for i = 0 to 24 do if x &&& (1 <<< i) <> 0 then b <- b ^^^ pattern.[i] if b = 0 then printAnswer x let lo board = for i = 1 to 25 do printfn "----- %d -----" i combinations (fun x -> check board x) 25 i // 高速化 let lo1 board = for i = 0 to 31 do let mutable b = board let mutable p = 0 // 1 行目を押す for j = 0 to 4 do if i &&& (1 <<< j) <> 0 then ( b <- b ^^^ pattern.[j] p <- p ||| (1 <<< j) ) // 2 - 5 行目を消す for j = 0 to 19 do if b &&& (1 <<< j) <> 0 then ( b <- b ^^^ pattern.[j + 5] p <- p ||| (1 <<< (j + 5)) ) if b = 0 then printAnswer p
F# の関数 printf はC言語の標準ライブラリ関数 printf に相当する関数です。いわゆる書式文字列を与えて、それに従ってデータを整形して出力します。これを「書式付き出力関数」といいます。ストリームのメソッド Write のように単純にデータを出力するのではなく、表示に関していろいろな指定を行うことができますが、その分使い方が少しだけ複雑になります。今回は書式付き出力関数について簡単に説明します。
書式付き出力関数は F# のモジュール Printf に用意されています。Printf はデフォルトで open されているので、関数を呼び出すとき Printf を付ける必要はありません。基本的は関数を以下に示します。
printf format args ... => stdout に出力 printfn format args ... => stdout に出力 (改行する) eprintf format args ...=> stderr に出力 eprintfn format args ...=> stderr に出力 (改行する) fprintf stream format args ... => stream に出力 fprintfn stream format args ... => stream に出力 (改行する) sprintf format args ... => string
printf は標準出力に、eprintf は標準エラー出力に、fprintf は stream に整形した結果を出力します。sprintf は整形した結果を文字列にして返します。名前の末尾に n が付く関数は最後に改行を出力します。
引数の文字列 format を書式文字列といい、出力に関する様々な指定を行います。書式文字列はそのまま文字列として扱われますが、文字列の途中にパーセント % が表れると、その後ろの文字を変換指示子として解釈し、引数に与えられたデータをその指示に従って表示します。
F# では % + 変換指示子 を「書式指定子」といいます。主な書式指定子を以下に示します。
%b 真偽値 (true, false) %s 文字列 %c 文字 (Unicode) %B, %d, %o, %x 整数 (2, 10, 8, 16 進数) %e, %f, %g 浮動小数点数、複素数 %A 任意の値 %% 記号 % を表す
それから、% と変換指示子の間にオプションでいろいろな設定を行うことができます。
これらのオプションは型指定子によって動作が異なる場合があります。
簡単な例を示しましょう。
> printfn "%d, %x, %o" 100 100 100;; 100, 64, 144 val it: unit = () > printfn "[%d]" 10;; [10] val it: unit = () > printfn "[%4d]" 10;; [ 10] val it: unit = () > printfn "[%4d]" 100000;; [100000] val it: unit = () > printfn "[%4d]" 123456;; [123456] val it: unit = () > printfn "[%-8d]" 123456;; [123456 ] val it: unit = () > printfn "[%08d]" 123456;; [00123456] val it: unit = ()
% の次の文字 d, x, o が変換指示子です。これらの指示子は整数を表示する働きをします。例が示すように、d は 10 進数、x は 16 進数、o は 8 進数で表示します。書式指定子の個数と与えるデータの数が合わないとエラーになるので注意してください。記号 % を出力したい場合は %% と続けて書きます。
整数値を表示する書式指定子は、データを表示するフィールド幅を指定することができます。最初の例がフィールド幅を指定しない場合で、次の例がフィールド幅を 4 に指定した場合です。10 ではフィールド幅に満たないので、右詰めに出力されています。もし、フィールド幅に収まらない場合は、指定を無視して数値を出力します。フィールド幅を 0 で埋めたい場合は、フラグに 0 を指定します。左詰めにしたい場合は、フラグに - を指定します。
%s は文字列を表示します。%s の場合でも、フィールド幅を指定することができます。簡単な例を示しましょう。
> printfn "%s" "hello, world";; hello, world val it: unit = () > printfn "[%s]" "hello, world";; [hello, world] val it: unit = () > printfn "[%20s]" "hello, world";; [ hello, world] val it: unit = () > printfn "[%-20s]" "hello, world";; [hello, world ] val it: unit = ()
%c は文字を表示します。%s と同様に、フィールド幅を指定することができます。簡単な例を示しましょう。
> printfn "%c" 'a';; a val it: unit = () > printfn "[%c]" 'a';; [a] val it: unit = () > printfn "[%4c]" 'a';; [ a] val it: unit = () > printfn "[%-4c]" 'a';; [a ] val it: unit = ()
F# (.NET) の場合、文字はユニコード (unicode) で表されます。日本語 (漢字やカナなど) を文字として扱うこともできます。もちろん、%c で表示することができます。
> printfn "%c" 'あ';; あ val it: unit = () > printfn "[%c]" 'あ';; [あ] val it: unit = () > printfn "[%4c]" 'あ';; [ あ] val it: unit = () > printfn "[%-4c]" 'あ';; [あ ] val it: unit = ()
F# の文字列は、配列のように角カッコを使って文字 (要素) を取り出すことができます。文字列の長さはプロパティ Length で求めることができますが、バイト数ではなく文字数になることに注意してください。
> let s1 = "abcde";; val s1: string = "abcde" > s1.Length;; val it: int = 5 > let s2 = "あいうえお";; val s2: string = "あいうえお" > s2.Length;; val it: int = 5 > for i = 0 to 4 do printfn "%c" s1.[i];; a b c d e val it: unit = () > for i = 0 to 4 do printfn "%c" s2.[i];; あ い う え お val it: unit = ()
浮動小数点数や複素数を表示するには %e, %f, %g を使います。
小数点の右側に印字される桁数は、%e と %f ではデフォルトで 6 桁になります。%g は整数部を含めて 6 桁で表示されます。これを変更するには精度を使います。精度はピリオド ( . ) のあとに数字を指定します。たとえば、"%.14f" とすると、小数点は 14 桁で表示されます。%g の場合、整数部を含めた桁数になります。小数点数の桁ではないので注意してください。
簡単な例を示します。
> let pi = System.Math.PI;; val pi: float = 3.141592654 > printfn "%e" pi;; 3.141593e+000 val it: unit = () > printfn "%.14e" pi;; 3.14159265358979e+000 val it: unit = () > printfn "%f" pi;; 3.141593 val it: unit = () > printfn "%.14f" pi;; 3.14159265358979 val it: unit = () > printfn "%g" pi;; 3.14159 val it: unit = () > printfn "%.6g" pi;; 3.14159 val it: unit = () > printfn "%.14g" pi;; 3.1415926535898 val it: unit = ()
%A は任意のデータを表示します。整数は 10 進数で表示されますが、型を表す接尾辞が付加されることがあります。
> printfn "%d" 123;; 123 val it: unit = () > printfn "%A" 123;; 123 val it: unit = () > printfn "%d" 123L;; 123 val it: unit = () > printfn "%A" 123L;; 123L
浮動小数点数は %.10g で表示されます。
> printfn "%g" pi;; 3.14159 val it: unit = () > printfn "%A" pi;; 3.141592654 val it: unit = () > printfn "%.10g" pi;; 3.141592654 val it: unit = ()
文字列の場合、%A は " で囲って表示されます。文字の場合は ' で囲って表示されます。
> printfn "%s" "hello, world";; hello, world val it: unit = () > printfn "%A" "hello, world";; "hello, world" val it: unit = () > printfn "%c" 'A';; A val it: unit = () > printfn "%A" 'A';; 'A' val it: unit = ()
他のデータ型の場合、基本的にはメソッド ToString を呼び出してデータを文字列に変換して出力します。
> type Foo(a: int) = - let x = a;; type Foo = new: a: int -> Foo > type Bar(a: int) = - let x = a - override this.ToString() = sprintf "Bar(%d)" x;; type Bar = new: a: int -> Bar override ToString: unit -> string > Foo(10) |> printfn "%A";; FSI_0053+Foo val it: unit = () > Bar(10) |> printfn "%A";; Bar(10) val it: unit = ()
このほかにも、書式付き出力関数にはいろいろな機能があります。詳細は F# のマニュアル プレーンテキスト形式の書式設定 をお読みくださいませ。