フォーマット出力

この章では、std.formatモジュールの機能について説明する。D言語のコア機能については説明しない。

stdというプレフィックスを持つすべてのモジュールと同様に、std.formatはDの標準ライブラリであるPhobos内のモジュールだ。この本ではPhobosを完全に解説するだけのスペースがない。

Dの入力および出力フォーマット指定子は、C言語のものと似ている。

さらに進む前に、ご参考までにフォーマット指定子とフラグを要約しておく。

フラグ(組み合わせ可能)
-左揃え
+符号を表示する
#別の方法で表示する
0ゼロ埋め表示
スペーススペース埋め表示
フォーマット指定子
s8進数
bバイナリ
d10進数
o8進数
xX16進数
fF標準的な十進表記法における浮動小数点数
eE科学記法における浮動小数点数
aA16進数記法における浮動小数点数
gGeまたはf
,数字の区切り文字
(要素フォーマット開始
)要素フォーマット終了
|要素区切り文字

これまで、必要な出力結果を表示するために、writelnのような複数のパラメータを持つ関数を使用してきた。パラメータは文字列に変換されてから 出力に送られる。

場合によっては、これだけでは不十分だ。出力は、非常に特定の形式である必要がある場合がある。請求書の項目を出力するために使用される次のコードを見てみよう:

items ~= 1.23;
items ~= 45.6;

for (int i = 0; i != items.length; ++i) {
    writeln("Item ", i + 1, ": ", items[i]);
}
D

出力:

アイテム11.23
アイテム245.6

情報は正しいものの、別の形式で出力する必要がある場合がある。例えば、小数点(この場合はドット)を揃える必要があり、小数点の後には常に2桁の数字が来るようにする必要がある場合などである。

アイテム1
    1.23
アイテム2
   45.60

このような場合、フォーマットされた出力は便利だ。これまで使用してきた出力関数には、名前にfという文字を含む対応する関数がある。writef()およびwritefln()である。fは、formatted(フォーマット)の略だ。これらの関数の最初のパラメータは、他のパラメータの出力方法を記述するフォーマット文字列である

例えば、writefln()は、次のフォーマット文字列を使用して、上記の出力を作成することができる。

writefln("Item %d:%9.02f", i + 1, items[i]);
D

フォーマット文字列には、出力にそのまま渡される通常の文字と、表示される各パラメーターに対応する特殊なフォーマット指定子が含まれる。フォーマット指定子は、%文字で始まり、フォーマット文字で終わる。上記のフォーマット文字列には、%d%9.02fの2つのフォーマット指定子がある。

各指定子は、通常、出現順に、それぞれのパラメータに関連付けられる。例えば、%di + 1に関連付けられ、%9.02fitems[i]に関連付けられる。各指定子は、それに対応するパラメータのフォーマットを指定する。(フォーマット指定子にはパラメータ番号も付けることができる。これについては、この章の後半で説明する。)

フォーマット文字列のうち、フォーマット指定子の一部ではないすべての文字は、そのまま表示される。上記のフォーマット指定子内の通常の文字は、 "Item %d:%9.02f"で強調表示されている。

フォーマット指定子はいくつかの部分で構成されており、そのほとんどはオプションである。位置という部分は、後で説明する。その他は次の通りだ。(注釈:これらの部分の間のスペースは、読みやすくするために挿入したもので、指定子の一部ではない。)

%フラグセパレーター精度フォーマット文字

先頭の%文字と末尾のformat文字は必須で、他の部分はオプションだ。

%はフォーマット文字列では特別な意味を持つため、%を通常の文字として出力するには、%%と入力する必要がある。

フォーマット文字

b: 整数引数は2進数で出力される。

o: 整数引数は8進数で表示される。

xおよびX: 整数引数は16進数で表示される。xを使用する場合は小文字で、Xを使用する場合は大文字で表示される。

d: 整数引数は10進数で出力される。符号付き型で値が0未満の場合は、負の符号も出力される。

int value = 12;

writefln("Binary     : %b", value);
writefln("Octal      : %o", value);
writefln("Hexadecimal: %x", value);
writefln("Decimal    : %d", value);
D
バイナリ1100
8進数14
16進数c
10進数12

e: 浮動小数点数の引数は、以下の規則に従って表示される。

E:eと同様だが、eの代わりにEを出力する。

fおよびF: 浮動小数点引数は10進数で出力される。小数点の前には少なくとも1桁の数字があり、小数点以下の桁数はデフォルトで6桁になる。

g fと同じ。ただし、指数が-5からprecisionの間である場合。それ以外はeと同じ。precisionは、小数点以下の桁数を指定するのではなく、値全体の有効桁数を指定する。小数点以下の有効桁がない場合、小数点は出力されない。小数点以下の右端の0は出力されない。

G:gと同様だが、文字Eを出力しない。

a: 浮動小数点引数は、16進浮動小数点表記で出力される:

A:aと同様だが、0XPの文字を出力しない。

double value = 123.456789;

writefln("with e: %e", value);
writefln("with f: %f", value);
writefln("with g: %g", value);
writefln("with a: %a", value);
D
eとともに1.234568e+02
fとともに123.456789
gとともに123.457
aとともに0x1.edd3c07ee0b0bp+6

s: 値は、引数の型に応じて、通常の出力と同じように表示される。

bool b = true;
int i = 365;
double d = 9.87;
string s = "formatted";
auto o = File("test_file", "r");
int[] a = [ 2, 4, 6, 8 ];

writefln("bool  : %s", b);
writefln("int   : %s", i);
writefln("double: %s", d);
writefln("string: %s", s);
writefln("object: %s", o);
writefln("array : %s", a);
D
booltrue
int365
double9.87
stringformatted
objectFile(55738FA0)
array[2, 4, 6, 8]
width

この部分は、引数が表示されるフィールドの幅を決定する。幅が文字*として指定されている場合、実際の幅の値は次の引数から読み込まれる (その引数はintでなければならない)。幅が負の値の場合、-フラグが指定されているとみなされる。

int value = 100;

writefln("In a field of 10 characters:%10s", value);
writefln("In a field of 5 characters :%5s", value);
D
10文字のフィールド
       100
5文字のフィールド
  100
separator

カンマ文字は、数値の桁をグループで区切るために使用される。グループのデフォルトの桁数は3だが、カンマの後に指定することができる:

writefln("%,f", 1234.5678);        // 3つのグループ
writefln("%,s", 1000000);          // 3つのグループ
writefln("%,2s", 1000000);         // 2つのグループ
D
1,234.567,800
1,000,000
1,00,00,00

桁数を*で指定した場合、実際の桁数は次の引数から読み込まれる(その引数はintでなければならない)。

writefln("%,*s", 1, 1000000);      // 1つのグループ
D
1,0,0,0,0,0,0

同様に、コンマの後ろに疑問符を付け、数字の前に追加の引数として文字を指定することで、区切り文字を指定することもできる:

writefln("%,?s", '.', 1000000);    // 区切り文字は'.'である。
D
1.000.000
precision

精度は、フォーマット指定子のドットの後に指定する。浮動小数点型の場合、値の出力表現の精度を決定する。精度が文字*として指定された場合、実際の精度は次の引数から読み込まれる (その引数はintでなければならない)。負の精度値は無視される。

double value = 1234.56789;

writefln("%.8g", value);
writefln("%.3g", value);
writefln("%.8f", value);
writefln("%.3f", value);
D
1234.5679
1.23e+03
1234.56789000
1234.568
auto number = 0.123456789;
writefln("Number: %.*g", 4, number);
D
番号0.1235
フラグ

複数のフラグを指定できる。

-: 値はそのフィールドの左端に揃えて表示される。このフラグは、0フラグを無効にする。

int value = 123;

writefln("Normally right-aligned:|%10d|", value);
writefln("Left-aligned          :|%-10d|", value);
D
通常右揃え
|       123|
左揃え
|123       |

+: 値が正の場合、その前に+文字が付けられる。このフラグはスペースフラグをキャンセルする。

writefln("No effect for negative values    : %+d", -50);
writefln("Positive value with the + flag   : %+d", 50);
writefln("Positive value without the + flag: %d", 50);
D
負の値には効果がない-50
+フラグが付いた正の値+50
+フラグなしの正の値50

#:format_characterに応じて、値を別の形式で出力する。

writefln("Octal starts with 0                        : %#o", 1000);
writefln("Hexadecimal starts with 0x                 : %#x", 1000);
writefln("Contains decimal mark even when unnecessary: %#g", 1f);
writefln("Rightmost zeros are printed                : %#g", 1.2);
D
8進数は0から始まる01750
16進数は0xで始まる0x3e8
小数点記号が不要な場合でも含まれる1.00000
右端のゼロが表示される1.20000

0: フィールドは0で埋められる(値がnanまたはinfinityでない場合)。精度も指定されている場合、このフラグは無視される。

writefln("In a field of 8 characters: %08d", 42);
D
8文字のフィールド00000042

スペース文字: 値が正の場合、負の値と正の値を揃えるために、先頭にスペース文字が挿入される

writefln("No effect for negative values: % d", -34);
writefln("Positive value with space    : % d", 56);
writefln("Positive value without space : %d", 56);
D
負の値には効果がない-34
正の値とスペース56
スペースなしの正の値56
位置パラメーター

上記で、引数がフォーマット文字列の指定子と1つずつ関連付けられることを説明した。フォーマット指定子内で位置番号を使用することも可能だ。これにより、指定子を特定の引数に関連付けることができる。引数は1から順に番号が付けられる。引数の番号は、%文字の直後に指定し、$で区切る:

%位置$フラグ精度フォーマット文字

位置パラメーターの利点は、同じフォーマット文字列内で同じ引数を複数の場所で使用できることだ:

writefln("%1$d %1$x %1$o %1$b", 42);
D

上記のフォーマット文字列では、4つの指定子内で番号1の引数を使用して、10進数、16進数、8進数、2進数の形式で出力している:

10進数16進数8進数2進数
422a52101010

位置パラメータのもう1つの用途は、複数の自然言語のサポートだ。位置番号で参照される引数は、特定の言語の特定のフォーマット文字列内の任意の場所に移動できる。例えば、特定の教室の生徒数を次のように出力できる。

writefln("There are %s students in room %s.", count, room);
D
1A教室には20人の生徒がいる。

プログラムがトルコ語もサポートする必要があると仮定しよう。この場合、フォーマット文字列はアクティブな言語に応じて選択する必要がある。次のメソッドは三項演算子を活用している:

auto format = (language == "en"
               ? "There are %s students in room %s."
               : "%s sınıfında %s öğrenci var.");

    writefln(format, count, room);
D

残念ながら、引数を1つずつ関連付けると、トルコ語のメッセージでは教室と生徒数の情報が逆順で表示される。教室の情報が生徒数の位置にあり、生徒数が教室の位置にある:

20 sınıfında 1A öğrenci var.  ← 間違い: "20組"と"1Aの生徒"を意味する!

これを回避するには、引数を番号で指定し、1$2$のように、各指定子を正確な引数と関連付けることができる:

auto format = (language == "en"
               ? "There are %1$s students in room %2$s."
               : "%2$s sınıfında %1$s öğrenci var.");

    writefln(format, count, room);
D

これで、選択した言語に関係なく、引数が正しい順序で表示されるようになった:

1A教室には20人の生徒がいる。
1A sınıfında 20 öğrenci var.
フォーマットされた要素の出力

フォーマット指定子は %(%)の間のフォーマット指定子は、コンテナ(例: 配列や範囲)のすべての要素に適用される:

auto numbers = [ 1, 2, 3, 4 ];
writefln("%(%s%)", numbers);
D

上記のフォーマット文字列は3つの部分から構成されている:

各部分は、 %s要素は順番に表示される:

1234

要素形式の前後の通常の文字は、各要素について繰り返される。例えば、 {%s},指定子は、波括弧で囲まれた要素をカンマで区切って表示する:

writefln("%({%s},%)", numbers);
D

ただし、フォーマット指定子の右側の通常の文字は要素区切り文字とみなされ、要素の間のみ表示され、最後の要素の後は表示されない:

{1},{2},{3},{4  ← '}'と','は最後の要素の後に表示されない

%|は、最後の要素でも表示されるべき文字を指定するために使用される。 %|の文字は区切り文字とみなされ、最後の要素では表示されない。逆に、 %|の文字は最後の要素でも表示される。

例えば、次のフォーマット指定子では、最後の要素の後に閉じ中括弧は表示されるが、コンマは表示されない。

writefln("%({%s}%|,%)", numbers);
D
{1},{2},{3},{4}  ← '}'は最後の要素の後に表示される

個々に表示される文字列とは異なり、要素として表示される文字列は二重引用符で囲まれて表示される:

auto vegetables = [ "spinach", "asparagus", "artichoke" ];
writefln("%(%s, %)", vegetables);
D
"spinach""asparagus""artichoke"

ダブルクォートを不要にする場合は、要素フォーマットを %-(で開始する必要がある。 %(で開始する必要がある:

writefln("%-(%s, %)", vegetables);
D
spinachasparagusartichoke

文字にも同じことが適用される。 %(はそれらをシングルクォートで囲んで出力する:

writefln("%(%s%)", "hello");
D
'h''e''l''l''o'

%-(はそれらを引用符なしで出力する:

writefln("%-(%s%)", "hello");
D
hello

連想配列には、2つのフォーマット指定子が必要である。1つはキー用、もう1つは値用だ。例えば、次の %s (%s)指定子は、まずキーを、次に値を括弧で囲んで出力する。

auto spelled = [ 1 : "one", 10 : "ten", 100 : "hundred" ];
writefln("%-(%s (%s)%|, %)", spelled);
D

また、指定されている場合、最後の要素のコンマは表示されないことに注意。 %|指定されているため、最後の要素のコンマは表示されないことに注意しよう:

1 (one),100 (hundred), 10 (ten)
format

フォーマットされた出力は、std.stringモジュールにあるformat()関数でも利用できる。format()writef()と同じように動作するが、結果を出力に表示するのではなく、stringとして返す。

import std.stdio;
import std.string;

void main() {
    write("What is your name? ");
    auto name = strip(readln());

    auto result = format("Hello %s!", name);
}
D
formatted_output.1

この結果は、後の式で使うことができる。

フォーマット文字列のチェック

標準ライブラリには、フォーマット文字列 (writefwriteflnformattedWritereadfformattedReadなど) を受け取る、formatのような関数の代替構文がある。これらの関数には、フォーマット文字列をテンプレート引数として指定して、フォーマット文字列と引数の有効性をコンパイル時にチェックすることができる。

import std.stdio;

void main() {
    writefln!"%s %s"(1);       // ← コンパイルエラー(余分な%s)
    writefln!"%s"(1, 2);       // ← コンパイルエラー(余分な2)
    writefln!"%s %d"(1, 2.5);  // ← コンパイルエラー(%dと2.5が一致しない)
}
D
formatted_output.2

上記の!文字はテンプレートインスタンス化演算子で、後で説明する。

(注釈:この構文は、コンパイル時にプログラマーの潜在的なエラーを検出できるため、より安全だが、コンパイル時間が長くなる可能性がある。

演習
  1. 値を読み込んで16進数で出力するプログラムを書いてみよう。
  2. 浮動小数点値を読み込み、小数点以下2桁のパーセンテージ値として出力するプログラムを作成しよう。例えば、値が1.2345の場合、%1.23と出力される必要がある。