{ }codelper
絶対値浮動小数点の誤差P0134

部品寸法の公差検査

工場で作った部品の長さを測り、図面の基準の長さと比べる。
一行目に基準の長さ N と公差 T が空白で区切って与えられる(単位 mm)。
二行目に部品の数 n が与えられ、続く n 行に 1 番の部品から順に測った長さが一つずつ与えられる。
長さと公差はすべて小数点以下をちょうど三桁で書いた数である(例:25.400、0.050)。
部品の差は(測った長さ - N)である。
差の大きさ(絶対値)が T 以下なら合格、T より大きければ不合格である。
まず部品ごとの差を一行に一つずつ出力せよ。
差は小数点以下三桁で書き、0 より大きければ前に "+"、0 より小さければ "-" を付け、0 なら符号なしで 0.000 と書く。
次の行に合格した部品の数を出力せよ。
次の行に不合格の部品の番号を小さい順に空白一つずつで区切って出力し、なければ "なし" を出力せよ。
最後の行に、差の大きさが最も大きい部品の番号を出力せよ(同じなら番号の小さい方)。

入力
0.001 ≤ N ≤ 1000.000、0.000 ≤ T ≤ 10.000、1 ≤ n ≤ 1,000、0.000 ≤ 測った長さ ≤ 2000.000
出力
n 行の差、合格数、不合格の番号(または "なし")、差が最も大きい番号
例 1 · stdin
25.400 0.050
5
25.412
25.450
25.351
25.400
25.349
stdout
+0.012
+0.050
-0.049
0.000
-0.051
4
5
5
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

落とし穴は、ちょうど境目にある部品です。
25.450 と 25.400 を実数(double)で読んで引くと 0.05000000000000071 になって 0.05 より大きくなり、合格のはずの部品が落ちます。
0.05 や 25.45 のような小数は二進数で正確に書けないので、八つの言語どれでも double を使えば同じことが起きます(Craykon の数も JS と同じ double です)。
解決策は、小数点以下がいつも三桁という約束を使って 1/1000 mm 単位の整数で読むことです。
点を消して整数にすれば "25.450" は 25450 です(Python int(s.replace(".", ""))、JS・TS Number(s.replace(".", ""))、Java Integer.parseInt(s.replace(".", ""))、C# int.Parse(s.Replace(".", ""))、C scanf("%d.%d")、C++ std::stoi、Craykon 行.置き換え(".", "").数に())。
絶対値は Python abs、JS・TS Math.abs、Java Math.abs、C# Math.Abs、C abs(<stdlib.h>、int 用)、C++ std::abs、Craykon 数学.絶対値 です。
C で実数の絶対値は fabs(<math.h>)という別の関数です。
絶対値を抜いて d <= T と聞くと、基準より短い部品はどれだけ短くても合格になります。
負の差を書くときに二つ目の落とし穴があります。
-50(= -0.050)をそのまま 1000 で割ると、C・C++・Java・C#・JS(Math.trunc)は 0 の方へ切り捨てるので商が 0 になって符号が消え、Python の // は切り下げなので商が -1、% の余りが 950 になって "-1.950" になります(JS の Math.floor も商は -1 ですが % は -50 なので、また別の形で間違えます)。
だから符号を先に書き、絶対値を商と余りに分け、余りは前に 0 を詰めて三桁にします。

Pythonmain.py · 30 行
def milli(s):    # "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다    return int(s.replace(".", ""))  def signed(d):    # 부호를 따로 떼고 절댓값으로 자리를 나눈다(-50 // 1000은 -1이라 그대로 나누면 틀린다)    sign = "+" if d > 0 else "-" if d < 0 else ""    a = abs(d)    return f"{sign}{a // 1000}.{a % 1000:03d}"  nominal, tol = map(milli, input().split())n = int(input())passed = 0failed = []worst = 1worst_dev = -1for k in range(1, n + 1):    d = milli(input().strip()) - nominal    print(signed(d))    if abs(d) <= tol:        passed += 1    else:        failed.append(k)    if abs(d) > worst_dev:  # 같으면 앞 번호를 둔다        worst, worst_dev = k, abs(d)print(passed)print(" ".join(map(str, failed)) if failed else "なし")print(worst)
JavaScriptmain.js · 27 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");// "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다const milli = (s) => Number(s.trim().replace(".", ""));const signed = (d) => {  const sign = d > 0 ? "+" : d < 0 ? "-" : "";  const a = Math.abs(d); // 부호를 떼고 자리를 나눈다  return sign + Math.floor(a / 1000) + "." + String(a % 1000).padStart(3, "0");};const [nominal, tol] = lines[0].trim().split(/\s+/).map(milli);const n = Number(lines[1]);const out = [];let passed = 0;const failed = [];let worst = 1;let worstDev = -1;for (let k = 1; k <= n; k++) {  const d = milli(lines[1 + k]) - nominal;  out.push(signed(d));  if (Math.abs(d) <= tol) passed++;  else failed.push(k);  if (Math.abs(d) > worstDev) {    worst = k; // 같으면 앞 번호를 둔다    worstDev = Math.abs(d);  }}out.push(String(passed), failed.length ? failed.join(" ") : "なし", String(worst));console.log(out.join("\n"));
TypeScriptmain.ts · 28 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");// "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다const milli = (s: string): number => Number(s.trim().replace(".", ""));const signed = (d: number): string => {  const sign = d > 0 ? "+" : d < 0 ? "-" : "";  const a = Math.abs(d); // 부호를 떼고 자리를 나눈다  return sign + Math.floor(a / 1000) + "." + String(a % 1000).padStart(3, "0");};const [nominal, tol] = lines[0].trim().split(/\s+/).map(milli);const n = Number(lines[1]);const out: string[] = [];let passed = 0;const failed: number[] = [];let worst = 1;let worstDev = -1;for (let k = 1; k <= n; k++) {  const d = milli(lines[1 + k]) - nominal;  out.push(signed(d));  if (Math.abs(d) <= tol) passed++;  else failed.push(k);  if (Math.abs(d) > worstDev) {    worst = k; // 같으면 앞 번호를 둔다    worstDev = Math.abs(d);  }}out.push(String(passed), failed.length ? failed.join(" ") : "なし", String(worst));console.log(out.join("\n"));
JavaMain.java · 39 行
import java.io.BufferedReader;import java.io.InputStreamReader; public class Main {    // "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다    static int milli(String s) {        return Integer.parseInt(s.trim().replace(".", ""));    }     static String signed(int d) {        String sign = d > 0 ? "+" : d < 0 ? "-" : "";        int a = Math.abs(d); // 부호를 떼고 자리를 나눈다        return sign + a / 1000 + "." + String.format("%03d", a % 1000);    }     public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        String[] first = in.readLine().trim().split("\\s+");        int nominal = milli(first[0]), tol = milli(first[1]);        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        StringBuilder failed = new StringBuilder();        int passed = 0, worst = 1, worstDev = -1;        for (int k = 1; k <= n; k++) {            int d = milli(in.readLine()) - nominal;            out.append(signed(d)).append('\n');            if (Math.abs(d) <= tol) passed++;            else failed.append(failed.length() > 0 ? " " : "").append(k);            if (Math.abs(d) > worstDev) { // 같으면 앞 번호를 둔다                worst = k;                worstDev = Math.abs(d);            }        }        out.append(passed).append('\n');        out.append(failed.length() > 0 ? failed.toString() : "なし").append('\n');        out.append(worst).append('\n');        System.out.print(out);    }}
C#Main.cs · 42 行
using System;using System.Collections.Generic;using System.Text; class Program{    // "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다    static int Milli(string s) => int.Parse(s.Trim().Replace(".", ""));     static string Signed(int d)    {        string sign = d > 0 ? "+" : d < 0 ? "-" : "";        int a = Math.Abs(d); // 부호를 떼고 자리를 나눈다        return $"{sign}{a / 1000}.{a % 1000:D3}";    }     static void Main()    {        string[] first = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int nominal = Milli(first[0]), tol = Milli(first[1]);        int n = int.Parse(Console.ReadLine());        var sb = new StringBuilder();        var failed = new List<int>();        int passed = 0, worst = 1, worstDev = -1;        for (int k = 1; k <= n; k++)        {            int d = Milli(Console.ReadLine()) - nominal;            sb.Append(Signed(d)).Append('\n');            if (Math.Abs(d) <= tol) passed++;            else failed.Add(k);            if (Math.Abs(d) > worstDev) // 같으면 앞 번호를 둔다            {                worst = k;                worstDev = Math.Abs(d);            }        }        sb.Append(passed).Append('\n');        sb.Append(failed.Count > 0 ? string.Join(" ", failed) : "なし").Append('\n');        sb.Append(worst).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 37 行
#include <cstdlib>#include <iomanip>#include <iostream>#include <string>#include <vector> // "25.400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다int milli(const std::string& s) {    auto dot = s.find('.');    return std::stoi(s.substr(0, dot)) * 1000 + std::stoi(s.substr(dot + 1));} int main() {    std::string a, b;    int n;    std::cin >> a >> b >> n;    int nominal = milli(a), tol = milli(b);    std::vector<int> failed;    int passed = 0, worst = 1, worstDev = -1;    for (int k = 1; k <= n; k++) {        std::string s;        std::cin >> s;        int d = milli(s) - nominal;        int dev = std::abs(d); // 부호를 떼고 자리를 나눈다        std::cout << (d > 0 ? "+" : d < 0 ? "-" : "") << dev / 1000 << '.' << std::setw(3) << std::setfill('0') << dev % 1000 << '\n';        if (dev <= tol) passed++;        else failed.push_back(k);        if (dev > worstDev) { // 같으면 앞 번호를 둔다            worst = k;            worstDev = dev;        }    }    std::cout << passed << '\n';    if (failed.empty()) std::cout << "なし";    for (size_t i = 0; i < failed.size(); i++) std::cout << (i ? " " : "") << failed[i];    std::cout << '\n' << worst << '\n';}
Cmain.c · 35 行
#include <stdio.h>#include <stdlib.h> /* "25.400"을 정수 25와 400으로 따로 읽는다 — 소수는 언제나 세 자리라 400은 1/1000 단위다 */int read_milli(void) {    int whole, frac;    scanf("%d.%d", &whole, &frac);    return whole * 1000 + frac;} int main(void) {    int nominal = read_milli();    int tol = read_milli();    int n;    scanf("%d", &n);    static int failed[1000];    int nfailed = 0, passed = 0, worst = 1, worst_dev = -1;    for (int k = 1; k <= n; k++) {        int d = read_milli() - nominal;        int a = abs(d); /* 정수 절댓값(실수라면 fabs) */        /* 부호를 따로 쓰고 절댓값으로 자리를 나눈다 */        printf("%s%d.%03d\n", d > 0 ? "+" : d < 0 ? "-" : "", a / 1000, a % 1000);        if (a <= tol) passed++;        else failed[nfailed++] = k;        if (a > worst_dev) { /* 같으면 앞 번호를 둔다 */            worst = k;            worst_dev = a;        }    }    printf("%d\n", passed);    if (nfailed == 0) printf("なし");    for (int i = 0; i < nfailed; i++) printf(i ? " %d" : "%d", failed[i]);    printf("\n%d\n", worst);    return 0;}
Craykonmain.cray · 57 行
천분(줄){    // "25.400" → "25400" → 25400 (소수점 아래는 언제나 세 자리): 실수로 바꾸지 않아 오차가 없다    返す 줄.空白除去().置き換え(".", "").数に();} 부호붙이기(d){    부호 = "";    もし (d > 0)    {        부호 = "+";    }    でなければ もし (d < 0)    {        부호 = "-";    }    a = 数学.絶対値(d); // 부호를 떼고 자리를 나눈다    返す 부호 + 数学.商(a, 1000) + "." + ("" + a % 1000).前埋め(3, "0");} first = コンソール.行読み().分ける();nominal = 천분(first[0]);tol = 천분(first[1]);n = コンソール.行読み().数に();passed = 0;failed = [];worst = 1;worstDev = -1;繰り返す (k = 1; k <= n; k++){    d = 천분(コンソール.行読み()) - nominal;    コンソール.書く(부호붙이기(d));    もし (数学.絶対値(d) <= tol)    {        passed += 1;    }    でなければ    {        failed.追加(k);    }    もし (数学.絶対値(d) > worstDev)    {        worst = k; // 같으면 앞 번호를 둔다        worstDev = 数学.絶対値(d);    }}コンソール.書く(passed);もし (failed.個数 > 0){    コンソール.書く(failed);}でなければ{    コンソール.書く("なし");}コンソール.書く(worst);