Official

A - Batting Average Editorial by Nyaan


この問題の本質部分だけを取り上げると「小数が与えられます。小数点第 \(4\) 位を四捨五入して出力することができますか?」となります。
小数の四捨五入は日常ではありふれていますが、コンテストでは見慣れない操作なので色々調べながら解いた方も多いと思います。
この解説では、プログラミングにおいて小数がどのような存在なのかを確認しながら方法を説明していきます。

浮動小数点数型の基本

多くのプログラミング言語には小数を管理するための型が存在します。C や C++ の double 型や Python の float 型がその代表例です。
このような小数型は少し特殊な方法で値を管理しています。すなわち、double 型の変数が 値 \(x\) を保持しているとき、ある整数 \(a\) および \(1 \leq b \lt 2\) である実数 \(b\) であって \(x = 2^{a} \times b\) を満たすような数の組 \((a, b)\) がメモリに格納されています。
これは int などの整数型が値をそのまま変数に保持しているのとは対照的で、このように \(a\) の値が \(x\) の値に応じて移動することから、このような小数の型を 浮動小数点数型 (floating-point data type) のように呼びます。

  • たとえば \(6 = 2^2 \times 1.5\) なので、内部では \((a,b) = (2, 1.5)\) という値の組として保持されています。

整数から小数、あるいはその逆への変換は キャスト演算子 を利用して変換することができます。
例えば C++ では int 型の xdouble 型に変換するには double(x) とすればよいです。よって、この問題の \(S\) を求める部分は以下のように記述すればよいです。

int main() {
  int A, B;
  cin >> A >> B;
  double S = double(B) / double(A);
}

C

まず、C 言語の printf 関数で double 型の小数を出力する場合は、以下のように 出力フォーマット指定子 として %lf を指定すればよいです。

  double S = ((double)B) / ((double)A);
  printf("%lf", S);

この問題では小数点以下をちょうど \(3\) 桁表示して、それ以降の桁は四捨五入する必要があります。これは出力フォーマット指定子を %.3lf とすると要求を満たすことができます。 (本当は値によっては四捨五入と異なる値が出力されますが、ここでは問題にならないので置いておきます。)

#include <stdio.h>
int main() {
  int A, B;
  scanf("%d %d", &A, &B);
  double S = ((double)B) / ((double)A);
  printf("%.3lf", S);
}

C++

C++ で小数を出力する方法はいくつかあります。1 つは C 言語と同様に printf を使う方法です。C++ では(ライブラリを適切に include すれば) C の標準ライブラリがそのまま使えるので、上述の出力フォーマット指定子を利用すればよいです。
std::cout を使う方法も説明します。これは少し難しいのですが、

cout << fixed << setprecision(3);

のように書くと所望の条件を満たすことができます。

  • 詳細な説明:fixed1e9 のような指数表記を利用しないことを指示しています。fixed が指定された状態で setprecision(n) を指示すると、それ以降の小数は 小数点以下ちょうど \(n\) 桁表示されて、それ以降の桁は四捨五入されます。(こちらも正確には少しだけ四捨五入とは異なりますがここでは問題にならないので置いておきます。)
#include <iomanip>
#include <iostream>
using namespace std;
int main() {
  int A, B;
  cin >> A >> B;
  double S = double(B) / double(A);
  cout << fixed << setprecision(3) << S << endl;
}

Python

Python では f-string と呼ばれるリテラルによって出力の桁数を指定できます。詳しくは以下のリファレンスなどをご参照ください。

A, B = map(int, input().split())
S = B / A
print(f'{S:.3f}')

余談:コンテストで「小数点第 \(n\) 位を四捨五入して出力」が出題されない理由

「小数点第 \(n\) 位を四捨五入」という操作は、日常生活でも野球の打率をはじめ各所で見かける基本的な処理で、学校の算数や数学・理科の問題でも答えの小数部のどこかを四捨五入して答案に記入する形式の問題は非常に多いです。ところが、競技プログラミングでは「答えを小数点第 \(4\) 位で四捨五入して出力」という形式での問題はほとんど存在せず、かわりに

想定解答との絶対誤差または相対誤差が \(10^{-6}\) 以下であれば正解として扱われる。

というような特殊な形式によるジャッジが行われています。これはなぜでしょうか?

これは 浮動小数点数型の演算の性質に起因します。整数の世界では \(a \leq x \leq b\) のように \(x\) を不等号で挟んでしまえば \(x\) として取り得る値は有限ですが、実数の世界は取り得る値が無限に存在します。これをどうにかプログラミングの枠組みで扱うために、 double 型 (Python の float も同様) は端数を上手く四捨五入して \(x = 2^{a} \times b\) という形に無理やり押し込んで取り扱っているため、 演算を行うたびに微細な 誤差 が発生して、計算を重ねるごとに誤差がどんどん積み重なっていきます。(例えば double 型では、四則演算を 1 回呼び出すたびに最大で \(10^{-16}\) 程度の相対誤差が発生します。)

ところが、この「誤差が発生する」という性質は「四捨五入して出力」という形式と非常に相性が悪いです。
例えばこの問題の制約が \(1 \leq A \leq 100\) で、\((A, B) = (80, 1)\) の場合を考えてみましょう。このとき \(S = \frac{1}{80} = 0.0125\) で、小数点第 \(4\) 位がちょうど \(5\) なので切り上げて 0.013 が答えとなります。ところが、\( B / A\) を計算する過程で \(S\) がわずかに小さくなって \(0.012499999999999999\) になってしまったとします。すると小数第 \(4\) 位は \(4\) になり、これを四捨五入すると答えは 0.012 になり、誤った答えを出力してしまいます。

このような例を見てもわかる通り、四捨五入という形式は 「値によっては少しでも誤差が発生すると答えが変わる」という性質を持っています。よって、「小数を四捨五入して出力」という問題は「誤差が発生しないようにしながら小数を扱ってください」という自家撞着したキテレツな問題になってしまうわけです。

  • ここまでの主張はやや正確ではなく、もう少し厳密な話をすると、小数の計算過程で蓄積する誤差だけでなく、丸め誤差と呼ばれる浮動小数点数の性質に起因する誤差や、最近接偶数丸め(いわゆる「銀行家の丸め」)と呼ばれる特殊な四捨五入の形式への配慮が必要になると考えられます。(難しい話なのでここでは説明を控えます)

以上のような理由から、日常生活で馴染みのある「小数を切り捨てた値を求める」という操作は、プログラミングの世界では思わぬ難題となってしまいます。こうした観点をおざなりにしたまま四捨五入をコンテストで出題すると、想定解の正当性が保証できなかったり、誤差をキチンと理解しているコンテスタントが大きく不利になったりしてしまいます。よって AtCoder のような運営に良識のあるコンテストサイトならば、このような問題は事前に reject されて出題されない、というのがコンテストに出てこない理由だと考えられます。ただしこの問題は例外で、与えられた制約下では答えが \(0.001 n + 0.0005\) (\(n\) は整数) という形で表されないこと、および小数の誤差が四捨五入後の結果を変えるほどは大きくならないことが証明できるので特別に出題を認められたと考えています。

posted:
last update: