利率の変換(年利・月利・日利)

2進小数の誤差0.5 の丸め方

言語ごとの結果

正解表 17件のうち、正解と一致した件数です。

PHPPHP 8.4.26

よくある書き方15 / 17

直した書き方17 / 17

PythonPython 3.14.7

よくある書き方14 / 17

直した書き方17 / 17

GoGo 1.27.2

よくある書き方14 / 17

直した書き方17 / 17

JavaScriptNode 24.15.0

よくある書き方14 / 17

直した書き方17 / 17

JavaJava 21.0.12

よくある書き方15 / 17

直した書き方17 / 17

C#.NET 10.0.402

よくある書き方14 / 17

直した書き方17 / 17

RustRust 1.99.0

よくある書き方14 / 17

直した書き方17 / 17

RubyRuby 4.0.7

よくある書き方15 / 17

直した書き方17 / 17

KotlinKotlin 2.4.21

よくある書き方15 / 17

直した書き方17 / 17

SwiftSwift 6.3.3

よくある書き方14 / 17

直した書き方17 / 17

なぜ割れたのか

よくある書き方が落ちたケースを、原因ごとにまとめた解説です(2本)。書いてある「落ちた言語」が今回の検証結果と一致することを確かめてから載せています。

2進小数の誤差

年利12.003%の月利 1.00025 を、10言語すべてが 1.0003 ではなく 1.0002 と表示した

対象のケースc005c006 落ちた言語PHPPythonGoJavaScriptJavaC#RustRubyKotlinSwift

年利 12.003%(c005)を単利で月利に直すと、12.003 ÷ 12 = 1.00025 です。有効数字5桁に四捨五入して 1.0003 が正解ですが、10言語すべてが 1.0002 と表示しました。

日利 0.33%(c006)の単利の月利 0.33 × 365 ÷ 12 = 10.0375 では、言語で答えが割れました。

ケース正確な値正解小さい方に丸めた言語
c005 年利 12.003%1.000251.000310言語すべて
c006 日利 0.33%10.037510.0386言語(C#・Go・JavaScript・Python・Rust・Swift)

公開中の元ツールも同じで、0.01刻みの入力では、年利 0.01〜1000%(10万通り)中 7,096通り(すべて年利120%以上)、日利 0.01〜1%(100通り)中 5通りで、単利の月利が正確な四捨五入と違います。

なぜずれたのか

2進数の小数で計算すると、1.00025 は 1.0002499999999999、10.0375 は 10.037499999999999… と、どちらも …5 のわずかに下になります。

小数の桁を決めて書く書式は、言語によって丸め方が違います。

  • C#(ToString("F"))・Go(strconv.FormatFloat)・JavaScript(toFixed)・Python(f"{:.3f}")・Rust(format!)・Swift(String(format:)): 2進数の値を正確な10進に直してから丸めます。…5 より少しでも小さければ、必ず小さい方になります
  • Java・Kotlin(String.format): 2進数の値を、元に戻せる最も短い10進(10.0375)にしてから丸めます。c006 は、たまたま 10.038 と正しく出ました
  • PHP(number_format): 1000倍してから丸めます。10.0374999…× 1000 が掛け算の誤差で 10037.5 ちょうどになり、c006 はたまたま正しく出ました
  • Ruby(format): c006 は 10.038 と出ました(C の printf は 10.037)。Ruby の書式は、2進数の正確な値で丸めないことがあります

c005 は、1.00025 × 10⁴ が掛け算でも 10002.499999999998 になるなど、どの言語でも助けがなく、10言語すべてが小さい方になりました。c006 で4言語が通ったのは偶然で、正しく丸めているわけではありません。

どう直したか

利率の文字列を、整数(分子)と10のべき(分母)の正確な分数として読み、÷12・÷365 も分数のまま計算して、「0.5 ちょうどは大きい方へ」四捨五入しました。有効数字5桁の桁数を決める「10の何乗か」も、log10 ではなく、10のべきとの整数の比較で決めています。Python は fractions.Fraction、Ruby は Rational、Java・Kotlin・C#・Go は多倍長整数、JavaScript は BigInt を使いました。

Rust と Swift は標準に多倍長整数が無いので、整数部4桁・小数8桁より下の桁は、右から「繰り上がり」だけを計算する筆算で求めています。入力の小数が何桁あってもあふれません。

2進小数の誤差

月利 0.123455% を入れると、複利の月利が 0.12346 ではなく 0.12345 になった

対象のケースc016 落ちた言語PHPPythonGoJavaScriptJavaC#RustRubyKotlinSwift

月利 0.123455% を入れたとき(c016)、複利の月利は、数式の上では入力そのものの 0.123455 です。有効数字5桁に四捨五入して 0.12346 が正解ですが、10言語すべてが 0.12345 と表示しました。

公開中の元ツールも同じで、0.12345 と出します。

なぜずれたのか

元ツールと同じ素直な書き方では、まず月利を年利に直し((1 + 月利)^12 − 1)、そこから月利に戻します((1 + 年利)^(1/12) − 1)。2進数の小数で12乗して12乗根を取ると、入力の 0.123455 に戻らず、わずかに小さくなります。…5 ちょうどの値がわずかに小さくなると、四捨五入は小さい方になります。

同じ単位どうしでも、計算をひと回りさせると、丸めの境目で答えが変わります。

どう直したか

入力と同じ単位の複利の値は、計算し直さずに入力そのもの(正確な分数)を四捨五入しました。月利で入れたときの複利の月利・日利で入れたときの複利の日利・年利で入れたときの複利の年利がこれにあたります。

入力と違う単位の複利(累乗・累乗根)は、10言語とも2進数の小数で計算しています。累乗根は無理数になり、整数の累乗も小数が10桁以上続くので、有効数字5桁の …5 ちょうどにはなりません。0.01刻みの入力(年利 0〜1000%・月利 0〜100%・日利 0〜1% の 111,003通り)で、10言語とも正解と一致することを確かめました。

とても小さい利率(c017。0.0000000000000000001%)では、2進数の小数で計算した累乗根が 0 になり、0 の対数を取ろうとして止まる書き方があります(最初に書いた Python の v2 がそうでした)。計算した値が 0 以下なら "0" と書くようにしています。

正解表と、よくある書き方の答え

正解は実装より先に決めて固定しています。右の欄は、よくある書き方の答えを言語ごとにまとめたものです(赤い値は実際に返した間違った答え)。

ケース入力正解よくある書き方の答え(10言語)
c001基本・年利12% 単利: 月利 1・日利 0.032877。複利: 月利 1.12^(1/12) − 1 = 0.94888…%
rate
12
unit
year
simple_year
12
simple_month
1
simple_day
0.032877
compound_year
12
compound_month
0.94888
compound_day
0.031054

✓10言語すべて正解

c002月利1%を年利に 複利の年利 1.01^12 − 1 = 12.682503…%
rate
1
unit
month
simple_year
12
simple_month
1
simple_day
0.032877
compound_year
12.683
compound_month
1
compound_day
0.032719

✓10言語すべて正解

c003日利0.05%を年利に
rate
0.05
unit
day
simple_year
18.25
simple_month
1.5208
simple_day
0.05
compound_year
20.016
compound_month
1.5321
compound_day
0.05

✓10言語すべて正解

c004遅延損害金・年利14.6% 単利の日利 14.6 ÷ 365 = 0.04 ちょうど
rate
14.6
unit
year
simple_year
14.6
simple_month
1.2167
simple_day
0.04
compound_year
14.6
compound_month
1.1421
compound_day
0.037343

✓10言語すべて正解

c005単利の月利が .xxxx5 ちょうど 12.003 ÷ 12 = 1.00025 → 1.0003。2進数の小数で計算すると 1.0002499999999999 になり、元ツールは 1.0002 と出す なぜ? 解説を読む
rate
12.003
unit
year
simple_year
12.003
simple_month
1.0003
simple_day
0.032885
compound_year
12.003
compound_month
0.9491
compound_day
0.031061
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftsimple_month"1.0002"
c006単利の月利が .xxx5 ちょうど(日利から) 0.33 × 365 ÷ 12 = 10.0375 → 10.038。元ツールは 10.037 なぜ? 解説を読む
rate
0.33
unit
day
simple_year
120.45
simple_month
10.038
simple_day
0.33
compound_year
232.85
compound_month
10.54
compound_day
0.33
  • Python・Go・JavaScript・C#・Rust・Swiftsimple_month"10.037"
  • PHP・Java・Ruby・Kotlin✓ 正解
c0070% すべて 0
rate
0
unit
year
simple_year
0
simple_month
0
simple_day
0
compound_year
0
compound_month
0
compound_day
0

✓10言語すべて正解

c008日利の単利がとても小さい 0.001 ÷ 365 = 0.00000273… は小数8桁まで(0.00000274)。有効数字は3桁になる
rate
0.001
unit
year
simple_year
0.001
simple_month
0.00008333
simple_day
0.00000274
compound_year
0.001
compound_month
0.00008333
compound_day
0.00000274

✓10言語すべて正解

c009四捨五入で1桁上がる 単利の月利 9.99995 は小数4桁で四捨五入して 10.0000 → 10
rate
119.9994
unit
year
simple_year
120
simple_month
10
simple_day
0.32877
compound_year
120
compound_month
6.7911
compound_day
0.21625

✓10言語すべて正解

c010年利の上限ちょうど
rate
1000
unit
year
simple_year
1000
simple_month
83.333
simple_day
2.7397
compound_year
1000
compound_month
22.119
compound_day
0.65912

✓10言語すべて正解

c011月利100%(複利の年利が大きい) 2^12 − 1 = 4095 倍 → 409500。小数は2桁まで出すので、最後の0を取って整数になる
rate
100
unit
month
simple_year
1200
simple_month
100
simple_day
3.2877
compound_year
409500
compound_month
100
compound_day
2.305

✓10言語すべて正解

c012日利1%(上限ちょうど) 複利の年利 1.01^365 − 1 = 3678.34…%
rate
1
unit
day
simple_year
365
simple_month
30.417
simple_day
1
compound_year
3678.34
compound_month
35.345
compound_day
1

✓10言語すべて正解

c013上限を超える
rate
1.01
unit
day
error
out_of_range

✓10言語すべて正解

c014数でない
rate
12%
unit
year
error
invalid_input

✓10言語すべて正解

c015単位が違う
rate
12
unit
week
error
invalid_input

✓10言語すべて正解

c016月利で入れて月利の複利は入力そのもの (1.00123455^12)^(1/12) − 1 は 0.123455 ちょうど → 0.12346。累乗してから累乗根で戻すと、2進数の小数では少し小さくなり、元ツールは 0.12345 と出す なぜ? 解説を読む
rate
0.123455
unit
month
simple_year
1.4815
simple_month
0.12346
simple_day
0.0040588
compound_year
1.4916
compound_month
0.12346
compound_day
0.0040564
  • PHP・Python・Go・JavaScript・Java・C#・Rust・Ruby・Kotlin・Swiftcompound_month"0.12345"
c017とても小さい利率 どの値も小数8桁で四捨五入すると 0。float で累乗根を計算すると 0 になり、0 の対数を取ろうとして止まる書き方がある
rate
0.0000000000000000001
unit
year
simple_year
0
simple_month
0
simple_day
0
compound_year
0
compound_month
0
compound_day
0

✓10言語すべて正解

コード

「違い」では、よくある書き方から直した書き方へ、どの行をどう変えたかを示しています(赤が消した行、緑が足した行、濃い色が行の中で変わった部分)。計算の部分だけを載せ、入出力を受け渡す部分は除いています。

Rate.php+150 −25よくある書き方(e8c32d9)→ 直した書き方

@@ 変更前 3行目〜 / 変更後 3行目〜 @@
33
44 /**
55 * 利率の変換(年利・月利・日利)。
6− * v1: 利率を is_numeric で確かめて float に読み、単利は割り算、複利は ** で累乗・累乗根を計算して、
7− * 桁数を log10 の floor で決め、number_format で書いてから最後の0を取る素直な書き方。
6+ * v2: 利率の文字列を float に読まず、小数点を外した数字の列(分子)と 10^桁数(分母)の正確な分数として10進のまま読む。
7+ * 上限の比較も分子と「上限 × 10^桁数」の整数で比べる。
8+ * …5 ちょうどになりうる値(単利の3つと、入力と同じ単位の複利)は「分子 ÷ 分母」の整数の筆算で、
9+ * 桁 e を整数の比較で決め、0.5 ちょうどは大きい方へ四捨五入する。
10+ * 入力と違う単位の複利(累乗・累乗根)は float で計算する。累乗根は無理数になり、整数の累乗も小数が10桁以上続くので、
11+ * 有効数字5桁の …5 ちょうどにはならない。rate が 0 なら複利も "0"。
12+ * v1 の float では c005 の 12.003 ÷ 12 = 1.00025 が 1.0002499999999999 になって simple_month が 1.0002 に落ち、
13+ * c016 の月利 0.123455% を12乗してから12乗根で戻すと 0.123455 より少し小さくなって compound_month が 0.12345 に落ちた。
14+ * c006 の 0.33 × 365 ÷ 12 = 10.0375(float では 10.03749999…)は、number_format が「1000 倍してから丸める」ため
15+ * 掛け算の誤差で 10037.5 ちょうどになり、偶然 10.038 で通っていた(Go・JavaScript の v1 は 10.037 に落ちた)。
16+ * 長い入力では分子・分母が int の上限(約 9.2 × 10^18)を超えうるので、整数は数字の列のまま筆算する
17+ * (bcmath・gmp は入っていない環境があるので使わない)。
818 */
19+const DECIMAL = '/^[0-9]+(\.[0-9]+)?$/D'; // D: $ を末尾の改行の前で一致させない
920 const N = ['year' => 1, 'month' => 12, 'day' => 365];
10−const LIMIT = ['year' => 1000, 'month' => 100, 'day' => 1];
21+const LIMIT = ['year' => '1000', 'month' => '100', 'day' => '1'];
1122
12−function fmt(float $p): string
23+/** 先頭の 0 を外す(0 は "0") */
24+function digitsNorm(string $a): string
1325 {
14− if ($p == 0) {
26+ $a = ltrim($a, '0');
27+ return $a === '' ? '0' : $a;
28+}
29+
30+/** 0 以上の整数(数字の列)の大小。-1・0・1 */
31+function digitsCmp(string $a, string $b): int
32+{
33+ return strlen($a) <=> strlen($b) ?: strcmp($a, $b) <=> 0;
34+}
35+
36+function digitsAdd(string $a, string $b): string
37+{
38+ $n = max(strlen($a), strlen($b));
39+ $a = str_pad($a, $n, '0', STR_PAD_LEFT);
40+ $b = str_pad($b, $n, '0', STR_PAD_LEFT);
41+ $out = '';
42+ $carry = 0;
43+ for ($i = $n - 1; $i >= 0; $i--) {
44+ $s = (int) $a[$i] + (int) $b[$i] + $carry;
45+ $out = ($s % 10) . $out;
46+ $carry = intdiv($s, 10);
47+ }
48+ return digitsNorm(($carry ? '1' : '') . $out);
49+}
50+
51+/** $a - $b($a >= $b のとき) */
52+function digitsSub(string $a, string $b): string
53+{
54+ $b = str_pad($b, strlen($a), '0', STR_PAD_LEFT);
55+ $out = '';
56+ $borrow = 0;
57+ for ($i = strlen($a) - 1; $i >= 0; $i--) {
58+ $d = (int) $a[$i] - (int) $b[$i] - $borrow;
59+ $borrow = $d < 0 ? 1 : 0;
60+ $out = ($d + 10 * $borrow) . $out;
61+ }
62+ return digitsNorm($out);
63+}
64+
65+/** 数字の列 × 小さい int(365 まで) */
66+function digitsMulSmall(string $a, int $m): string
67+{
68+ $out = '';
69+ $carry = 0;
70+ for ($i = strlen($a) - 1; $i >= 0; $i--) {
71+ $s = (int) $a[$i] * $m + $carry;
72+ $out = ($s % 10) . $out;
73+ $carry = intdiv($s, 10);
74+ }
75+ return digitsNorm(($carry ? (string) $carry : '') . $out);
76+}
77+
78+/** 筆算の割り算。[商, 余り] */
79+function digitsDivMod(string $a, string $b): array
80+{
81+ $q = '';
82+ $r = '0';
83+ for ($i = 0, $n = strlen($a); $i < $n; $i++) {
84+ $r = digitsNorm($r . $a[$i]);
85+ $d = 0;
86+ while (digitsCmp($r, $b) >= 0) {
87+ $r = digitsSub($r, $b);
88+ $d++;
89+ }
90+ $q .= $d;
91+ }
92+ return [digitsNorm($q), $r];
93+}
94+
95+/** n ÷ 10^d を書き、小数の最後の0と小数点を取る */
96+function show(string $n, int $d): string
97+{
98+ $s = str_pad($n, $d + 1, '0', STR_PAD_LEFT);
99+ return rtrim(rtrim(substr($s, 0, -$d) . '.' . substr($s, -$d), '0'), '.');
100+}
101+
102+/** 小数の桁数 d(有効数字5桁・2以上8以下)。10^e <= 値 < 10^(e+1) の e を決めて 5 − e − 1 */
103+function places(int $e): int
104+{
105+ return min(max(5 - $e - 1, 2), 8);
106+}
107+
108+/**
109+ * 正確な値 $num ÷ ($den × 10^$scale)(0 以上)を四捨五入(0.5 ちょうどは大きい方へ)して書く。
110+ * $den は 1・12・365。
111+ */
112+function fmtExact(string $num, int $den, int $scale): string
113+{
114+ if ($num === '0') {
15115 return '0';
16116 }
17− $d = 5 - (int) floor(log10(abs($p))) - 1;
18− $d = min(max($d, 2), 8);
19− $s = number_format($p, $d, '.', '');
20− return rtrim(rtrim($s, '0'), '.');
117+ // e は -4 以下なら d = 8、2 以上なら d = 2 なので、-3〜2 の境目だけ比べればよい。
118+ // 値 >= 10^k ⇔ num × 10^3 >= den × 10^(scale + k + 3)
119+ $e = -4;
120+ for ($k = -3; $k <= 2; $k++) {
121+ if (digitsCmp($num . '000', $den . str_repeat('0', $scale + $k + 3)) >= 0) {
122+ $e = $k;
123+ }
124+ }
125+ $d = places($e);
126+ // 値 × 10^d = (num × 10^d) ÷ (den × 10^scale)。余りの2倍が割る数以上なら切り上げ
127+ $q = (string) $den . str_repeat('0', $scale);
128+ [$n, $r] = digitsDivMod($num . str_repeat('0', $d), $q);
129+ if (digitsCmp(digitsAdd($r, $r), $q) >= 0) {
130+ $n = digitsAdd($n, '1');
131+ }
132+ return show($n, $d);
133+}
134+
135+/** float の $p(…5 ちょうどにならない値)を書く */
136+function fmtFloat(float $p): string
137+{
138+ if ($p <= 0) {
139+ return '0';
140+ }
141+ $d = places((int) floor(log10($p)));
142+ return show(sprintf('%.0f', round($p * 10 ** $d)), $d);
21143 }
22144
23145 /** @param array{rate: string, unit: string} $in */
@@ 変更前 25行目〜 / 変更後 147行目〜 @@
25147 {
26148 $rate = $in['rate'] ?? null;
27149 $unit = $in['unit'] ?? null;
28− if (!is_string($unit) || !isset(N[$unit]) || !is_numeric($rate)) {
150+ if (!is_string($unit) || !isset(N[$unit]) || !is_string($rate) || !preg_match(DECIMAL, $rate)) {
29151 return ['error' => 'invalid_input'];
30152 }
31− $raw = (float) $rate;
32− if ($raw < 0) {
33− return ['error' => 'invalid_input'];
34− }
35− if ($raw > LIMIT[$unit]) {
153+ [$int, $frac] = explode('.', $rate . '.');
154+ $num = digitsNorm($int . $frac); // 利率(%)= num ÷ 10^scale
155+ $scale = strlen($frac);
156+ if (digitsCmp($num, LIMIT[$unit] . str_repeat('0', $scale)) > 0) {
36157 return ['error' => 'out_of_range'];
37158 }
38159 $n = N[$unit];
39− $r = $raw / 100;
40− $aSimple = $r * $n;
41− $aCompound = (1 + $r) ** $n - 1;
42− return [
43− 'simple_year' => fmt($aSimple * 100),
44− 'simple_month' => fmt($aSimple / 12 * 100),
45− 'simple_day' => fmt($aSimple / 365 * 100),
46− 'compound_year' => fmt($aCompound * 100),
47− 'compound_month' => fmt(((1 + $aCompound) ** (1 / 12) - 1) * 100),
48− 'compound_day' => fmt(((1 + $aCompound) ** (1 / 365) - 1) * 100),
160+ $simple = digitsMulSmall($num, $n); // 単利の年利(%)= simple ÷ 10^scale
161+ $out = [
162+ 'simple_year' => fmtExact($simple, 1, $scale),
163+ 'simple_month' => fmtExact($simple, 12, $scale),
164+ 'simple_day' => fmtExact($simple, 365, $scale),
49165 ];
166+ if ($num === '0') {
167+ return $out + ['compound_year' => '0', 'compound_month' => '0', 'compound_day' => '0'];
168+ }
169+ $g = (1 + (float) $rate / 100) ** $n; // 複利で1年に何倍になるか
170+ $same = fmtExact($num, 1, $scale);
171+ $out['compound_year'] = $unit === 'year' ? $same : fmtFloat(($g - 1) * 100);
172+ $out['compound_month'] = $unit === 'month' ? $same : fmtFloat(($g ** (1 / 12) - 1) * 100);
173+ $out['compound_day'] = $unit === 'day' ? $same : fmtFloat(($g ** (1 / 365) - 1) * 100);
174+ return $out;
50175 }

仕様

入力

項目型説明
rate文字列利率(%)。10進の数("12"・"0.05")
unit文字列rate の単位。"year"(年利)・"month"(月利)・"day"(日利)

出力

成功したとき(年利12%): {"simple_year": "12", "simple_month": "1", "simple_day": "0.032877", "compound_year": "12", "compound_month": "0.94888", "compound_day": "0.031054"}

1年は12か月・365日とする。r = rate ÷ 100、n = 1(年)・12(月)・365(日)として、

項目定義(%)
simple_year単利の年利 A = r × n × 100
simple_monthA ÷ 12
simple_dayA ÷ 365
compound_year複利の年利。G = (1 + r)^n として (G − 1) × 100
compound_month(G^(1/12) − 1) × 100
compound_day(G^(1/365) − 1) × 100
  • 入力と同じ単位の複利の値は、数式の上で入力そのものになる(月利で入れたときの compound_month = rate)

書き方(有効数字5桁)

どの値も、次のとおりに書く(元ツールの表示と同じ決め方。% は付けない)。

  1. 値が 0 なら "0"
  2. 小数の桁数 d = 5 − (10^e ≤ 値 < 10^(e+1) となる整数 e) − 1。ただし 2 より小さければ 2、8 より大きければ 8
  3. 丸める前の正確な値を小数 d 桁に四捨五入する。0.5 ちょうどは大きい方へ(1.00025 → 1.0003)
  4. 小数の最後の 0 と、最後に残った小数点を取る(1.0000 → 1・0.0400 → 0.04)
  • e は丸める前の値で決める。四捨五入で桁が上がっても d は変えない(9.99995 → d = 4 → 10.0000 → 10)
  • 値が 1000 以上なら小数2桁まで出すので、有効数字は5桁より多くなる(3678.34)。0.0001 未満なら小数8桁までなので、5桁より少なくなる(0.00000274)

判定できないとき:

  • {"error": "invalid_input"}: rate が10進の数として読めないとき(負の数・"12%" を含む)、unit が3つのどれでもないとき
  • {"error": "out_of_range"}: rate が上限を超えるとき。上限は年利 1000・月利 100・日利 1

元ツールとの違い

  • 元ツールは2進数の小数で計算してから toFixed で丸めるので、正確な値が …5 ちょうどになる入力で、小さい方に丸めることがある(上記)。本 spec は正確な値で丸める
  • 元ツールは月利・日利を、年利に直してから累乗根で戻すので、入力と同じ単位の複利の値が入力と少し違うことがある(月利 0.123455% の compound_month は 0.12346 が正しいが、元ツールは 0.12345)
  • 元ツールに上限は無い。日利 10% では複利の年利が 10^17 を超え、2進数の小数では整数の桁も正しく出せないので、本 spec は上限を決めて out_of_range とした

期待値の作り方

expect は oracle.py で、割り算と整数の累乗は Python の fractions.Fraction(正確な分数)で、累乗根は 120桁の十進(decimal)で計算して生成した(2026-11-02)。累乗根の値が四捨五入のちょうど真ん中に近いときは、分数でちょうど真ん中かを確かめている。実装ではない。runner は使わない。