{ }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);