C# の文字列フォーマットとは、数値や日付を「00123」「1,234,567」「2026/10/02」のような決まった見た目の文字列に変える仕組みです。 書き方は、文字列補間 $"{値:書式}"、値.ToString("書式")、string.Format("{0:書式}", 値) の3通りがあり、書式の文字(D5 や N0 など)は3つとも共通です。ふだんは文字列補間で足ります。

この記事では、「やりたいこと → 書式 → 出力結果」の早見表を最初に置きました。表の出力はすべて .NET 8 で実行したもので、3通りの書き方が同じ結果になることもプログラムで確認しています。 後半では、カルチャ(言語と地域の設定)で通貨記号や小数点が変わる例、丸め方の違い、FormatException の実際のメッセージも載せています。

文字列フォーマットの早見表

カルチャは ja-JP、変数は n が整数、d と r が double、dt が DateTime(2026年10月2日 9時5分7秒)です。ToString で書くなら n.ToString("D5")、string.Format なら string.Format("{0:D5}", n) と、同じ書式をそのまま使えます。

やりたいこと 書式 値 $ 補間 出力
0埋め(5桁) D5 123 $"{n:D5}" 00123
0埋め(カスタム) 00000 123 $"{n:00000}" 00123
桁区切り N0 1234567 $"{n:N0}" 1,234,567
桁区切り(カスタム) #,0 1234567 $"{n:#,0}" 1,234,567
小数2桁 F2 1234.5678 $"{d:F2}" 1234.57
小数2桁(カスタム) 0.00 1234.5678 $"{d:0.00}" 1234.57
桁区切り+小数2桁 N2 1234.5678 $"{d:N2}" 1,234.57
桁区切り+小数2桁(カスタム) #,0.00 1234.5678 $"{d:#,0.00}" 1,234.57
不要な0を出さない 0.## 1.5 $"{d:0.##}" 1.5
パーセント P 0.256 $"{r:P}" 25.600%
パーセント(小数なし) P0 0.256 $"{r:P0}" 26%
パーセント(小数1桁) P1 0.256 $"{r:P1}" 25.6%
通貨 C 1234567 $"{n:C}" ¥1,234,567
16進(大文字) X 255 $"{n:X}" FF
16進(小文字4桁) x4 255 $"{n:x4}" 00ff
2進(8桁、.NET 8 以降) B8 255 $"{n:B8}" 11111111
指数 E2 1234.5678 $"{d:E2}" 1.23E+003
符号を付ける +0;-0;0 5 $"{n:+0;-0;0}" +5
右寄せ(幅10) ,10 123 $"[{n,10}]" [ 123]
左寄せ(幅10) ,-10 123 $"[{n,-10}]" [123 ]
右寄せ+桁区切り ,10:N0 1234567 $"[{n,10:N0}]" [ 1,234,567]
日付と時刻 yyyy/MM/dd HH:mm:ss 2026-10-02 09:05:07 $"{dt:yyyy/MM/dd HH:mm:ss}" 2026/10/02 09:05:07
曜日つき yyyy/MM/dd(ddd) 同上 $"{dt:yyyy/MM/dd(ddd)}" 2026/10/02(金)
区切りなし8桁 yyyyMMdd 同上 $"{dt:yyyyMMdd}" 20261002
年月日(漢字) yyyy年M月d日 同上 $"{dt:yyyy年M月d日}" 2026年10月2日
ISO 8601 形式 s 同上 $"{dt:s}" 2026-10-02T09:05:07
書き方 形 例
文字列補間 $"{式,幅:書式}" $"{n,8:N0}"
ToString 値.ToString("書式") + 幅は PadLeft / PadRight n.ToString("N0").PadLeft(8)
string.Format string.Format("{番号,幅:書式}", 値) string.Format("{0,8:N0}", n)

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C#文字列フォーマット早見表|文字列補間$とToString書式・0埋め」

この記事のコードを動かす準備

検証環境は .NET SDK 8.0.131(ランタイム 8.0.31)、C# 12、Linux です。 dotnet new console -o FormatDemo でコンソールアプリを作り、Program.cs を各セクションのコードに置き換えて dotnet run で実行します。

通貨記号や日付の並びは、実行する環境のカルチャで変わります。今回の検証環境は既定のカルチャが InvariantCulture(特定の言語・地域に依存しない設定)だったため、コードの先頭で CultureInfo.CurrentCulture = new CultureInfo("ja-JP"); と明示しています。OS が日本語設定なら、この行がなくても ja-JP になるのが一般的です。ja-JP の通貨記号は今回の環境(Linux)では全角の ¥ で出力されました。OS やカルチャデータによって半角の ¥ になる場合があります。

3通りの書き方を同じ値で比べる

文字列補間・ToString・string.Format は、同じ書式なら同じ結果になります。

// Program.cs(.NET 8 / C# 12)
using System.Globalization;

CultureInfo.CurrentCulture = new CultureInfo("ja-JP");   // 出力をそろえるため明示

int id = 123;
int total = 1234567;
double average = 1234.5678;
double rate = 0.256;
var now = new DateTime(2026, 10, 2, 9, 5, 7);

// (1) 文字列補間:$"..." の { } に 式[,幅][:書式] を書く
Console.WriteLine($"ID={id:D5} 合計={total:N0} 平均={average:F2} 率={rate:P1} 日付={now:yyyy/MM/dd}");

// (2) ToString:値1つを文字列にする
Console.WriteLine("ID=" + id.ToString("D5") + " 合計=" + total.ToString("N0") + " 平均=" + average.ToString("F2")
    + " 率=" + rate.ToString("P1") + " 日付=" + now.ToString("yyyy/MM/dd"));

// (3) string.Format:{番号[,幅][:書式]} のひな形に、あとから値を渡す
Console.WriteLine(string.Format("ID={0:D5} 合計={1:N0} 平均={2:F2} 率={3:P1} 日付={4:yyyy/MM/dd}",
    id, total, average, rate, now));

// ひな形を変数や設定から受け取るときは string.Format が向く
string template = "{0}さん、{1:M月d日}のご注文は {2:N0} 点です。{0}さん、ありがとうございます。";
Console.WriteLine(string.Format(template, "田中", now, 12));

// 補間の { } には式が書ける。条件演算子は ( ) で囲む
Console.WriteLine($"2倍={id * 2} 判定={(id >= 100 ? "3桁以上" : "2桁以下")} 大文字={"abc".ToUpper()}");

実行結果(.NET 8):

ID=00123 合計=1,234,567 平均=1234.57 率=25.6% 日付=2026/10/02
ID=00123 合計=1,234,567 平均=1234.57 率=25.6% 日付=2026/10/02
ID=00123 合計=1,234,567 平均=1234.57 率=25.6% 日付=2026/10/02
田中さん、10月2日のご注文は 12 点です。田中さん、ありがとうございます。
2倍=246 判定=3桁以上 大文字=ABC
書き方 向いている場面 気を付ける点
文字列補間 $"..." ふだんの文字列の組み立て。まずこれを選ぶ 式が { } の中に直接見えるので、番号のずれが起きない
ToString("書式") 値1つだけを文字列にする、カルチャを引数で渡す 幅の指定はできないので PadLeft / PadRight を使う
string.Format ひな形を変数・設定ファイル・リソースから受け取る、同じ値を何度も使う 番号と引数の数が合わないと FormatException

補間の { } には、変数だけでなく計算やメソッド呼び出しも書けます。条件演算子 ? : は、: が書式の区切りと見分けられるように ( ) で囲みます。

数値の書式:0埋め・桁区切り・小数・%・16進

数値の書式には、1文字+桁数の「標準書式」(D5、N0、F2)と、0 や # で形を描く「カスタム書式」(00000、#,0.00)があります。

using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("ja-JP");

// 0埋め
Console.WriteLine($"D5={123:D5} 00000={123:00000} 桁が多いとき={1234567:D5} 負の数={-123:D5}");
Console.WriteLine($"文字列の0埋め PadLeft={"42".PadLeft(5, '0')} 右を埋める PadRight=[{"ab".PadRight(5, '*')}]");
Console.WriteLine($"小数の0埋め 000.00={7.5:000.00} F2 は整数部を埋めない={7.5:F2}");

// 桁区切りと小数
Console.WriteLine($"N0={1234567:N0} N2={1234567.891:N2} #,0={1234567:#,0} #,0.00={1234.5:#,0.00}");
Console.WriteLine($"0.00={1.5:0.00} 0.##={1.5:0.##} 0.##={2.0:0.##} #.##={0.5:#.##} 0.##={0.5:0.##}");
Console.WriteLine($"int に F2={5:F2} decimal に N1={98.5m:N1} long に N0={long.MaxValue:N0}");

// パーセント・通貨・16進・2進・指数
Console.WriteLine($"P={0.256:P} P0={0.256:P0} P1={0.256:P1} 0.0%={0.256:0.0%} 100倍しない={25.6:0.0}%");
Console.WriteLine($"C={1234567:C} C2={1234.5:C2}");
Console.WriteLine($"X={255:X} x={255:x} X4={255:X4} 0x付き=0x{255:X2} B8={5:B8}");
Console.WriteLine($"E2={1234.5678:E2} e3={0.000123:e3}");

// 正・負・ゼロで書式を分ける(セクション区切り ;)
foreach (int v in new[] { 5, -5, 0 })
    Console.WriteLine($"{v,3} → {v:+0;-0;0} / {v:#,0;▲#,0;-}");

// 文字列に数値用の書式を付けても、何も起きない(例外にもならない)
Console.WriteLine($"文字列に D5 → {"123":D5} / 数値に直してから → {int.Parse("123"):D5}");

実行結果(.NET 8、ja-JP):

D5=00123 00000=00123 桁が多いとき=1234567 負の数=-00123
文字列の0埋め PadLeft=00042 右を埋める PadRight=[ab***]
小数の0埋め 000.00=007.50 F2 は整数部を埋めない=7.50
N0=1,234,567 N2=1,234,567.89 #,0=1,234,567 #,0.00=1,234.50
0.00=1.50 0.##=1.5 0.##=2 #.##=.5 0.##=0.5
int に F2=5.00 decimal に N1=98.5 long に N0=9,223,372,036,854,775,807
P=25.600% P0=26% P1=25.6% 0.0%=25.6% 100倍しない=25.6%
C=¥1,234,567 C2=¥1,234.50
X=FF x=ff X4=00FF 0x付き=0xFF B8=00000101
E2=1.23E+003 e3=1.230e-004
  5 → +5 / 5
 -5 → -5 / ▲5
  0 → 0 / -
文字列に D5 → 123 / 数値に直してから → 00123
書式 意味 補足
D5 整数を5桁になるまで 0 で埋める 整数型専用。 桁が多い値はそのまま出る。負の数は -00123
00000 カスタム書式の 0 埋め 小数にも使える(000.00 → 007.50)
N0 / N2 桁区切り+小数桁数 区切り文字はカルチャで変わる
F2 小数2桁(桁区切りなし) 整数に付けると 5.00
0 と # 0 は桁がなくても 0 を出す、# は出さない 0.5 に #.## を使うと .5 になる。整数部には 0 を1つ置く
P0 / P1 100 倍して % を付ける 既に 25.6 のような値なら {x:0.0}% と自分で % を書く
C 通貨記号+桁区切り 記号と小数桁数はカルチャで決まる(ja-JP は小数なし)
X / x 16進(大文字/小文字) 整数型専用。X4 で4桁の 0 埋め
B8 2進(8桁) 整数型専用。.NET 8 以降
正;負;ゼロ ; で区切って符号ごとに書式を変える {v:#,0;▲#,0;-} のように使う

0 埋めで気を付けたいのは「文字列に D5 を付けても何も起きない」点です。 上の最終行のとおり、$"{"123":D5}" は 123 のままで、例外にもなりません。文字列を 0 埋めするときは "123".PadLeft(5, '0')、または数値に変換してから D5 を使います。

日付と時刻の書式:yyyy/MM/dd HH:mm:ss と曜日

日付は yyyy(年)MM(月)dd(日)HH(24時間)mm(分)ss(秒)を組み合わせます。大文字と小文字で意味が変わるので、表で確認してください。

using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("ja-JP");

var dt = new DateTime(2026, 10, 2, 15, 4, 5, 45);    // 2026年10月2日(金) 15:04:05.045

// カスタム書式
Console.WriteLine($"{dt:yyyy/MM/dd HH:mm:ss}   ← 24時間表記は HH");
Console.WriteLine($"{dt:yyyy/MM/dd hh:mm:ss tt} ← hh は12時間表記、tt は午前/午後");
Console.WriteLine($"{dt:yyyy-MM-dd} / {dt:yyyyMMdd} / {dt:yyyyMMddHHmmss} / {dt:HH:mm:ss.fff}");
Console.WriteLine($"{dt:yyyy年M月d日(ddd)} / {dt:dddd} / {dt:M/d H:m}");
Console.WriteLine($"{dt:yyyy/mm/dd}          ← mm は「分」。月は大文字の MM");

// 標準書式(1文字)
Console.WriteLine($"d={dt:d} | D={dt:D} | t={dt:t} | T={dt:T}");
Console.WriteLine($"g={dt:g} | G={dt:G}");
Console.WriteLine($"s={dt:s} | o={dt:o}");

// 1文字だけのカスタム書式は % を付ける(付けないと標準書式として解釈される)
Console.WriteLine($"M={dt:M} / %M={dt:%M} / d={dt:d} / %d={dt:%d}");

// 和暦
var jp = new CultureInfo("ja-JP");
jp.DateTimeFormat.Calendar = new JapaneseCalendar();
Console.WriteLine(dt.ToString("ggy年M月d日(ddd)", jp));

// TimeSpan・DateOnly・TimeOnly
var span = new TimeSpan(1, 2, 3, 4);
Console.WriteLine($"TimeSpan 既定={span} / {span:hh\\:mm\\:ss} / {span:d\\日hh\\時\\間} / 合計 {span.TotalHours:F1} 時間");
Console.WriteLine($"DateOnly={DateOnly.FromDateTime(dt):yyyy/MM/dd} TimeOnly={TimeOnly.FromDateTime(dt):HH:mm}");

実行結果(.NET 8、ja-JP):

2026/10/02 15:04:05   ← 24時間表記は HH
2026/10/02 03:04:05 午後 ← hh は12時間表記、tt は午前/午後
2026-10-02 / 20261002 / 20261002150405 / 15:04:05.045
2026年10月2日(金) / 金曜日 / 10/2 15:4
2026/04/02          ← mm は「分」。月は大文字の MM
d=2026/10/02 | D=2026年10月2日金曜日 | t=15:04 | T=15:04:05
g=2026/10/02 15:04 | G=2026/10/02 15:04:05
s=2026-10-02T15:04:05 | o=2026-10-02T15:04:05.0450000
M=10月2日 / %M=10 / d=2026/10/02 / %d=2
令和8年10月2日(金)
TimeSpan 既定=1.02:03:04 / 02:03:04 / 1日02時間 / 合計 26.1 時間
DateOnly=2026/10/02 TimeOnly=15:04
書式 意味 出力例
yyyy / yy 年(4桁/2桁) 2026 / 26
MM / M 月(0埋めあり/なし) 10
dd / d 日(0埋めあり/なし) 02 / 2
ddd / dddd 曜日(短い/長い) 金 / 金曜日
HH / hh 時(24時間/12時間) 15 / 03
mm 分 04
ss / fff 秒/ミリ秒 05 / 045
tt 午前/午後 午後
  • mm は分、MM は月です。 yyyy/mm/dd と書くと、上の出力のように 2026/04/02 と「分」が入ります。
  • hh は12時間表記なので、15時が 03 になります。24時間表記は HH です。
  • d や M を1文字だけで使うと「標準書式」として解釈されます(d は短い日付、M は月日)。1文字のカスタム書式として使うときは %d、%M と % を付けます。
  • TimeSpan の書式では、: や文字をそのまま出すときに \ でエスケープします(hh\:mm\:ss)。

右寄せ・左寄せで列をそろえる

{式,幅} の幅が正の数なら右寄せ、負の数なら左寄せです。 書式と一緒に使うときは {式,幅:書式} の順に書きます。

// {式,幅} … 正の数は右寄せ、負の数は左寄せ。書式と組み合わせるときは {式,幅:書式}
var items = new (string Code, int Quantity, decimal Price)[]
{
    ("NOTE-01", 12, 180m), ("PEN-BK", 150, 98.5m), ("TAG-S", 3, 1200m),
};

Console.WriteLine($"|{"Code",-8}|{"Qty",5}|{"Price",10}|");
Console.WriteLine($"|{new string('-', 8)}|{new string('-', 5)}|{new string('-', 10)}|");
foreach (var item in items)
    Console.WriteLine($"|{item.Code,-8}|{item.Quantity,5}|{item.Price,10:N1}|");

// 幅を変数で決めたいときは PadLeft / PadRight
int width = 12;
Console.WriteLine("[" + "PEN-BK".PadRight(width) + "]" + "[" + 98.5m.ToString("N1").PadLeft(width) + "]");

// 幅より長い値は切り詰められず、そのまま出る
Console.WriteLine($"[{"LONG-CODE-0001",-8}]");

// 全角文字も1文字として数えるため、等幅フォントでは見た目の幅がずれる
Console.WriteLine($"[{"ノート",-8}] 文字数={"ノート".Length}");
Console.WriteLine($"[{"NOTE",-8}] 文字数={"NOTE".Length}");

実行結果(.NET 8):

|Code    |  Qty|     Price|
|--------|-----|----------|
|NOTE-01 |   12|     180.0|
|PEN-BK  |  150|      98.5|
|TAG-S   |    3|   1,200.0|
[PEN-BK      ][        98.5]
[LONG-CODE-0001]
[ノート     ] 文字数=3
[NOTE    ] 文字数=4

幅は「最低限の文字数」なので、幅より長い値は切り詰められずにそのまま出ます。また、幅は文字数で数えるため、全角文字を含む列は等幅フォントでも見た目の幅がずれます(ノート は3文字として扱われ、空白が5つ足されています)。

カルチャで通貨記号・小数点・日付の並びが変わる

N2 や C、d といった標準書式の結果は、カルチャによって変わります。 同じ値を5つのカルチャで出力します。

using System.Globalization;

decimal price = 1234567.891m;
var dt = new DateTime(2026, 10, 2, 9, 5, 7);

Console.WriteLine($"{"カルチャ",-9} | {"C",-16} | {"N2",-14} | {"P1",-7} | {"d",-10} | ddd");
foreach (string name in new[] { "ja-JP", "en-US", "de-DE", "fr-FR", "" })
{
    var culture = new CultureInfo(name);          // "" は InvariantCulture
    string label = name == "" ? "Invariant" : name;
    Console.WriteLine($"{label,-9} | {price.ToString("C", culture),-16} | {price.ToString("N2", culture),-14} | "
        + $"{0.256.ToString("P1", culture),-7} | {dt.ToString("d", culture),-10} | {dt.ToString("ddd", culture)}");
}

// カルチャを指定する4つの方法
var de = new CultureInfo("de-DE");
Console.WriteLine(price.ToString("N2", de));                                    // ToString の第2引数
Console.WriteLine(string.Format(de, "{0:N2}", price));                          // string.Format の第1引数
Console.WriteLine(string.Create(de, $"{price:N2}"));                            // 補間 + string.Create(.NET 6 以降)
Console.WriteLine(FormattableString.Invariant($"{price:N2}"));                  // 補間を InvariantCulture で

// 何も指定しない補間・ToString は CultureInfo.CurrentCulture に従う
CultureInfo.CurrentCulture = de;
Console.WriteLine($"CurrentCulture=de-DE: {1234.5:N2} / {1.5} / {dt}");
Console.WriteLine($"double.Parse(\"1.5\") = {double.Parse("1.5")} ← de-DE では . が桁区切り");
Console.WriteLine(string.Create(CultureInfo.InvariantCulture,
    $"Invariant で読む: {double.Parse("1.5", CultureInfo.InvariantCulture)}"));

// 日付の / は「カルチャの日付区切り」に置き換わる。固定したいときは '/' と書く
Console.WriteLine($"{dt:yyyy/MM/dd} → 固定: {dt:yyyy'/'MM'/'dd} → Invariant: {dt.ToString("yyyy/MM/dd", CultureInfo.InvariantCulture)}");

実行結果(.NET 8):

カルチャ      | C                | N2             | P1      | d          | ddd
ja-JP     | ¥1,234,568       | 1,234,567.89   | 25.6%   | 2026/10/02 | 金
en-US     | $1,234,567.89    | 1,234,567.89   | 25.6%   | 10/2/2026  | Fri
de-DE     | 1.234.567,89 €   | 1.234.567,89   | 25,6 %  | 02.10.2026 | Fr
fr-FR     | 1 234 567,89 €   | 1 234 567,89   | 25,6 %  | 02/10/2026 | ven.
Invariant | ¤1,234,567.89    | 1,234,567.89   | 25.6 %  | 10/02/2026 | Fri
1.234.567,89
1.234.567,89
1.234.567,89
1,234,567.89
CurrentCulture=de-DE: 1.234,50 / 1,5 / 02.10.2026 09:05:07
double.Parse("1.5") = 15 ← de-DE では . が桁区切り
Invariant で読む: 1.5
2026.10.02 → 固定: 2026/10/02 → Invariant: 2026/10/02
違い ja-JP / en-US de-DE fr-FR
小数点 . , ,
桁区切り , . 空白
通貨記号の位置 前(¥ / $) 後ろ(€) 後ろ(€)
短い日付 d 2026/10/02 / 10/2/2026 02.10.2026 02/10/2026

何も指定しない補間や ToString は、そのとき設定されている CultureInfo.CurrentCulture に従います。 画面に表示する文字列ならそれが自然です。一方、ファイル・ログ・JSON・SQL などプログラムが読み戻す文字列は、CultureInfo.InvariantCulture を指定して環境に左右されない形にします。出力の後半にあるとおり、de-DE では double.Parse("1.5") が 15 になりました。. が桁区切りとして読まれたためです。

カルチャの指定方法 書き方
ToString の第2引数 price.ToString("N2", culture)
string.Format の第1引数 string.Format(culture, "{0:N2}", price)
補間+string.Create(.NET 6 以降) string.Create(culture, $"{price:N2}")
補間を InvariantCulture で FormattableString.Invariant($"{price:N2}")

日付のカスタム書式に書く / は「そのカルチャの日付区切り」を表すため、de-DE では 2026.10.02 になりました。必ず / で出したいときは yyyy'/'MM'/'dd と引用符で囲むか、InvariantCulture を指定します。

丸め方:F2とMath.Roundは同じ結果とは限らない

書式で小数桁を減らすと、はみ出た桁は丸められます。その丸め方は、Math.Round の既定の動きとは別です。 ちょうど中間になる値で比べます。

using System.Globalization;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

Console.WriteLine("--- double ---");
Console.WriteLine($"{"値",-6} | {"F2",-5} | {"0.00",-5} | {"Math.Round(v,2)",-15} | AwayFromZero");
foreach (double v in new[] { 0.125, 0.135, 2.675, 1.005 })
    Console.WriteLine($"{v,-6} | {v,-5:F2} | {v,-5:0.00} | {Math.Round(v, 2),-15} | {Math.Round(v, 2, MidpointRounding.AwayFromZero)}");

Console.WriteLine("--- decimal ---");
Console.WriteLine($"{"値",-6} | {"F2",-5} | {"0.00",-5} | {"Math.Round(v,2)",-15} | AwayFromZero");
foreach (decimal v in new[] { 0.125m, 0.135m, 2.675m, 1.005m })
    Console.WriteLine($"{v,-6} | {v,-5:F2} | {v,-5:0.00} | {Math.Round(v, 2),-15} | {Math.Round(v, 2, MidpointRounding.AwayFromZero)}");

Console.WriteLine("--- 整数への丸め ---");
foreach (decimal v in new[] { 0.5m, 1.5m, 2.5m, 3.5m })
    Console.WriteLine($"{v} | F0={v:F0} | Math.Round={Math.Round(v)} | AwayFromZero={Math.Round(v, MidpointRounding.AwayFromZero)} | (double)F0={(double)v:F0}");

Console.WriteLine("--- 切り捨て・切り上げ ---");
decimal x = 12.349m;
Console.WriteLine($"切り捨て={Math.Round(x, 2, MidpointRounding.ToZero)} 切り上げ={Math.Ceiling(x * 100) / 100} Floor={Math.Floor(x)} Truncate={Math.Truncate(-12.9m)} Floor(-12.9)={Math.Floor(-12.9m)}");

実行結果(.NET 8):

--- double ---
値      | F2    | 0.00  | Math.Round(v,2) | AwayFromZero
0.125  | 0.12  | 0.13  | 0.12            | 0.13
0.135  | 0.14  | 0.14  | 0.14            | 0.14
2.675  | 2.67  | 2.68  | 2.68            | 2.68
1.005  | 1.00  | 1.01  | 1               | 1
--- decimal ---
値      | F2    | 0.00  | Math.Round(v,2) | AwayFromZero
0.125  | 0.13  | 0.13  | 0.12            | 0.13
0.135  | 0.14  | 0.14  | 0.14            | 0.14
2.675  | 2.68  | 2.68  | 2.68            | 2.68
1.005  | 1.01  | 1.01  | 1.00            | 1.01
--- 整数への丸め ---
0.5 | F0=1 | Math.Round=0 | AwayFromZero=1 | (double)F0=0
1.5 | F0=2 | Math.Round=2 | AwayFromZero=2 | (double)F0=2
2.5 | F0=3 | Math.Round=2 | AwayFromZero=3 | (double)F0=2
3.5 | F0=4 | Math.Round=4 | AwayFromZero=4 | (double)F0=4
--- 切り捨て・切り上げ ---
切り捨て=12.34 切り上げ=12.35 Floor=12 Truncate=-12 Floor(-12.9)=-13
方法 中間の値の扱い(今回の実行結果)
Math.Round(v, 2)(既定) 偶数への丸め。 0.125m → 0.12、2.5m → 2、3.5m → 4
Math.Round(v, 2, MidpointRounding.AwayFromZero) いわゆる四捨五入。0.125m → 0.13、2.5m → 3
decimal の F2 / 0.00 / F0 四捨五入と同じ結果。0.125m → 0.13、2.5m → 3
double の F2 0.125 → 0.12、2.675 → 2.67、1.005 → 1.00
double の 0.00 0.125 → 0.13、2.675 → 2.68、1.005 → 1.01

double は 2 進数で値を持つため、2.675 や 1.005 は内部ではわずかに小さい値になっています。その影響もあり、double では F2 とカスタム書式 0.00 で結果が分かれました。 表示の桁をきちんと決めたい小数は、次の2点を押さえると結果が読みやすくなります。

  • 正確さが必要な小数は decimal で持つ(リテラルは 0.125m のように m を付ける)。
  • 丸め方を決めたいときは、先に Math.Round(v, 桁, MidpointRounding.XXX) で丸めてから書式化する。 切り捨ては MidpointRounding.ToZero、切り上げは Math.Ceiling を使います。

FormatExceptionが出る書き方と直し方

string.Format はひな形を実行時に解釈するので、書き間違いは実行して初めて FormatException になります。 文字列補間なら、同じ間違いの多くはコンパイル時に気付けます。

static void Try(string label, Func<string> action)
{
    try { Console.WriteLine($"{label}\n    → OK: {action()}"); }
    catch (Exception ex) { Console.WriteLine($"{label}\n    → {ex.GetType().Name}: {ex.Message}"); }
}

object[] one = { 1 };
string template;
var dt = new DateTime(2026, 10, 2);

template = "{0} / {1}";    Try("引数が足りない  string.Format(\"{0} / {1}\", 1)", () => string.Format(template, one));
template = "{name}";       Try("番号でなく名前  string.Format(\"{name}\", 1)", () => string.Format(template, one));
template = "金額 {0";      Try("閉じ忘れ        string.Format(\"金額 {0\", 1)", () => string.Format(template, one));
template = "集合 {a, b}";  Try("{ } をそのまま  string.Format(\"集合 {a, b}\", 1)", () => string.Format(template, one));
template = "a } b";        Try("} だけ          string.Format(\"a } b\", 1)", () => string.Format(template, one));
Try("型に合わない書式  1.5.ToString(\"D5\")", () => 1.5.ToString("D5"));
Try("知らない標準書式  dt.ToString(\"Q\")", () => dt.ToString("Q"));
Try("数字以外を Parse   int.Parse(\"abc\")", () => int.Parse("abc").ToString());
Try("桁区切りを Parse   int.Parse(\"1,234\")", () => int.Parse("1,234").ToString());
Try("範囲外を Parse     int.Parse(\"99999999999\")", () => int.Parse("99999999999").ToString());
Try("無い日付を Parse   DateTime.Parse(\"2026/13/01\")", () => DateTime.Parse("2026/13/01").ToString());

Console.WriteLine("--- 直した後 ---");
Console.WriteLine(string.Format("{0} / {1}", 1, 2));
Console.WriteLine(string.Format("集合 {{a, b}} の要素数は {0}", 2));
Console.WriteLine(((int)Math.Round(1.5)).ToString("D5") + " / " + 1.5.ToString("00000.0"));
Console.WriteLine(int.TryParse("abc", out int n) ? $"{n}" : "数値として読めませんでした");

実行結果(.NET 8):

引数が足りない  string.Format("{0} / {1}", 1)
    → FormatException: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
番号でなく名前  string.Format("{name}", 1)
    → FormatException: Input string was not in a correct format. Failure to parse near offset 1. Expected an ASCII digit.
閉じ忘れ        string.Format("金額 {0", 1)
    → FormatException: Input string was not in a correct format. Failure to parse near offset 5. Format item ends prematurely.
{ } をそのまま  string.Format("集合 {a, b}", 1)
    → FormatException: Input string was not in a correct format. Failure to parse near offset 4. Expected an ASCII digit.
} だけ          string.Format("a } b", 1)
    → FormatException: Input string was not in a correct format. Failure to parse near offset 3. Unexpected closing brace without a corresponding opening brace.
型に合わない書式  1.5.ToString("D5")
    → FormatException: Format specifier was invalid.
知らない標準書式  dt.ToString("Q")
    → FormatException: Input string was not in a correct format.
数字以外を Parse   int.Parse("abc")
    → FormatException: The input string 'abc' was not in a correct format.
桁区切りを Parse   int.Parse("1,234")
    → FormatException: The input string '1,234' was not in a correct format.
範囲外を Parse     int.Parse("99999999999")
    → OverflowException: Value was either too large or too small for an Int32.
無い日付を Parse   DateTime.Parse("2026/13/01")
    → FormatException: String '2026/13/01' was not recognized as a valid DateTime.
--- 直した後 ---
1 / 2
集合 {a, b} の要素数は 2
00002 / 00001.5
数値として読めませんでした
メッセージ 原因 直し方
Index (zero based) must be greater than or equal to zero and less than the size of the argument list. {1} に対応する引数がない 引数を足す。または補間に書き換える
Expected an ASCII digit. {name} や {a, b} のように、{ } の中が番号でない 文字として出すなら {{ }}。名前で書きたいなら補間 $"{name}"
Format item ends prematurely. } の閉じ忘れ } を足す
Unexpected closing brace without a corresponding opening brace. } だけがある 文字として出すなら }}
Format specifier was invalid. 型に合わない書式(double に D5) 整数に変換するか、00000 などカスタム書式にする
The input string 'abc' was not in a correct format. int.Parse に数字以外 int.TryParse で判定する

{ }のエスケープ、逐語的文字列 @、生文字列リテラル

{ と } を文字として出すときは {{ }} と2つ重ねます。 パスや JSON のように \ や " が多い文字列は、@ や """ を使うと読みやすく書けます。

using System.Globalization;
CultureInfo.CurrentCulture = new CultureInfo("ja-JP");

int id = 123;
int total = 1234567;
var dt = new DateTime(2026, 10, 2);

// { } を文字として出す:2つ重ねる
Console.WriteLine($"{{id}} の値は {id} / 波かっこで囲む {{{id}}}");
Console.WriteLine(string.Format("{{0}} の値は {0}", id));

// 逐語的文字列 @:\ をそのまま書ける。" は "" と重ねる
string path1 = "C:\\work\\data.csv";
string path2 = @"C:\work\data.csv";
Console.WriteLine($"{path1} / {path2} / 同じ={path1 == path2}");
Console.WriteLine(@"彼は ""OK"" と言った");
string dir = "logs";
Console.WriteLine($@"C:\{dir}\{dt:yyyyMMdd}.txt");            // $@ と @$ はどちらでも可

// 生文字列リテラル """(C# 11 以降):" も \ もそのまま。閉じる """ の位置までのインデントは取り除かれる
string json = """
    {
      "name": "田中",
      "path": "C:\work"
    }
    """;
Console.WriteLine(json);

// 生文字列 + 補間:$ を2つにすると {{ }} が補間、{ } はただの文字になる
string body = $$"""
    {
      "id": {{id}},
      "total": "{{total:N0}}",
      "note": "{そのまま}"
    }
    """;
Console.WriteLine(body);

// 定数どうしの補間は const にできる(C# 10 以降)
const string Prefix = "ORD";
const string Key = $"{Prefix}-KEY";
Console.WriteLine(Key);

// 補間の式に : を含むとき(条件演算子)は ( ) で囲む
Console.WriteLine($"判定: {(id > 100 ? "大" : "小")}");

実行結果(.NET 8、ja-JP):

{id} の値は 123 / 波かっこで囲む {123}
{0} の値は 123
C:\work\data.csv / C:\work\data.csv / 同じ=True
彼は "OK" と言った
C:\logs\20261002.txt
{
  "name": "田中",
  "path": "C:\work"
}
{
  "id": 123,
  "total": "1,234,567",
  "note": "{そのまま}"
}
ORD-KEY
判定: 大
書き方 使える版 \ " { } を文字として出す
"..." 最初から \\ \" そのまま
$"..." C# 6 以降 \\ \" {{ }}
@"..."(逐語的) 最初から そのまま "" そのまま
$@"..." C# 6 以降(@$ の順は C# 8 以降) そのまま "" {{ }}
"""..."""(生文字列) C# 11 以降 そのまま そのまま そのまま
$$"""...""" C# 11 以降 そのまま そのまま そのまま(補間は {{式}})

生文字列リテラルは、閉じる """ の位置までのインデントが各行から取り除かれます。 上の出力でも、コード上は4つ字下げしてある JSON が行頭から始まっています。$ の数が「補間に使う波かっこの数」になるので、JSON のように { } が多い文字列は $$""" にすると、{ をそのまま書けます。

文字列を数値・日付に戻す:TryParse

書式化の逆(文字列 → 数値・日付)は TryParse を使うと、読めない入力でも例外にならず false で受け取れます。

using System.Globalization;
var inv = CultureInfo.InvariantCulture;

// 数値:TryParse は読めたかどうかを bool で返す
Console.WriteLine($"int.TryParse(\"123\")   = {int.TryParse("123", out int a)} → {a}");
Console.WriteLine($"int.TryParse(\"abc\")   = {int.TryParse("abc", out int b)} → {b}");
Console.WriteLine($"int.TryParse(\"1,234\") = {int.TryParse("1,234", out int c)} → {c}");
Console.WriteLine($"  AllowThousands を指定 = {int.TryParse("1,234", NumberStyles.AllowThousands, inv, out int d)} → {d}");
Console.WriteLine($"decimal.TryParse(\"1234.50\") = {decimal.TryParse("1234.50", NumberStyles.Number, inv, out decimal e)} → {e.ToString(inv)}");
Console.WriteLine($"16進: int.Parse(\"FF\", HexNumber) = {int.Parse("FF", NumberStyles.HexNumber)} / Convert.ToInt32(\"ff\", 16) = {Convert.ToInt32("ff", 16)}");

// 日付:書式が決まっているなら TryParseExact
Console.WriteLine($"DateTime.TryParse(\"2026/10/02 09:05\") = {DateTime.TryParse("2026/10/02 09:05", inv, DateTimeStyles.None, out DateTime f)} → {f:yyyy-MM-dd HH:mm}");
Console.WriteLine($"TryParseExact(\"20261002\", \"yyyyMMdd\") = {DateTime.TryParseExact("20261002", "yyyyMMdd", inv, DateTimeStyles.None, out DateTime g)} → {g:yyyy-MM-dd}");
Console.WriteLine($"TryParseExact(\"2026/10/2\", \"yyyy/MM/dd\") = {DateTime.TryParseExact("2026/10/2", "yyyy/MM/dd", inv, DateTimeStyles.None, out _)} ← 桁数が合わない");
Console.WriteLine($"TryParseExact(\"2026/10/2\", \"yyyy/M/d\")   = {DateTime.TryParseExact("2026/10/2", "yyyy/M/d", inv, DateTimeStyles.None, out DateTime h)} → {h:yyyy-MM-dd}");

// 書式化 → パースで元に戻す(往復)
var now = new DateTime(2026, 10, 2, 9, 5, 7);
string text = now.ToString("o", inv);
DateTime back = DateTime.ParseExact(text, "o", inv, DateTimeStyles.RoundtripKind);
Console.WriteLine($"往復: {text} → 一致={back == now}");

実行結果(.NET 8):

int.TryParse("123")   = True → 123
int.TryParse("abc")   = False → 0
int.TryParse("1,234") = False → 0
  AllowThousands を指定 = True → 1234
decimal.TryParse("1234.50") = True → 1234.50
16進: int.Parse("FF", HexNumber) = 255 / Convert.ToInt32("ff", 16) = 255
DateTime.TryParse("2026/10/02 09:05") = True → 2026-10-02 09:05
TryParseExact("20261002", "yyyyMMdd") = True → 2026-10-02
TryParseExact("2026/10/2", "yyyy/MM/dd") = False ← 桁数が合わない
TryParseExact("2026/10/2", "yyyy/M/d")   = True → 2026-10-02
往復: 2026-10-02T09:05:07.0000000 → 一致=True
  • 入力の形が決まっている日付は TryParseExact で書式を指定します。yyyy/MM/dd は2桁の月日を要求するので、2026/10/2 は False になります。1桁も受け付けるなら yyyy/M/d です。
  • プログラム同士でやり取りする日時は、"o"(往復書式)で書き出して DateTimeStyles.RoundtripKind で読み戻すと、元の値に戻ります。

よくある質問

Q. 文字列補間と string.Format はどちらを使えばよいですか? A. ふだんは文字列補間で足ります。式がその場に見えるので番号のずれが起きず、書き間違いの多くにコンパイル時に気付けます。ひな形を設定ファイルやリソースから読み込む場合は、実行時に解釈できる string.Format を使います。

Q. 0埋めは D5 と 00000 のどちらがよいですか? A. 整数ならどちらでも同じ結果です(本記事の実行結果で 123 はどちらも 00123)。D5 は整数型専用で、double や decimal に付けると FormatException になります。小数を含む値や 000.00 のような形にしたいときはカスタム書式を使います。

Q. 環境によって日付や小数点の表示が変わるのはなぜですか? A. 何も指定しない書式化は CultureInfo.CurrentCulture に従うためです。画面表示はそのままで、ファイルやログに書き出す値は InvariantCulture を指定すると、どの環境でも同じ文字列になります。

まとめ

  • 書き方は補間 $"{値:書式}"・ToString("書式")・string.Format の3通り。 書式の文字は共通で、結果も同じ
  • 0埋めは D5 / 00000、桁区切りは N0 / #,0、小数は F2 / 0.00、% は P1、16進は X
  • 右寄せは {x,10}、左寄せは {x,-10}。 書式と併用するときは {x,10:N0}
  • 日付は yyyy/MM/dd HH:mm:ss。 MM は月で mm は分、HH は24時間で hh は12時間
  • 通貨記号・小数点・日付の並びはカルチャで変わる。 プログラムが読み戻す値は InvariantCulture を指定する
  • 丸め方は書式と Math.Round で異なる。 決めたいときは decimal と MidpointRounding を使う
  • { } は {{ }} で文字として出す。 \ や " が多い文字列は @"..." や """...""" が読みやすい

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「C#文字列フォーマット早見表|文字列補間$とToString書式・0埋め」

株式会社RJCでは、C# の文字列フォーマットや書式指定の使い方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?