题解:B2021 输出保留 3 位小数的浮点数

· · 题解

[ \color{#00B8D4} \rule{2pt}{44pt} \color{#E5F8FB} \rule[24pt]{365pt}{20pt} \color{#e8e8e8}\rule{0.5pt}{44pt} \color{#f5f5f5}\rule{0.5pt}{44pt} \color{#fafafa}\rule{0.5pt}{44pt} \kern{-365pt}\kern{-1.5pt} \color{#bfbfbf}\rule[0pt]{365pt}{0pt}\kern{-365pt} \color{#d6d6d6}\rule[-0.5pt]{365pt}{0pt}\kern{-365pt} \color{#ececec}\rule[-1pt]{365pt}{0pt}\kern{-365pt} \color{#f8f8f8}\rule[-1.5pt]{365pt}{0pt}\kern{-365pt} \color{black} \raisebox{24pt}{ \raisebox{6pt}{ \kern{-1pt} \color{#00B8D4}\large{\kern{2pt}\bf{i}\kern{5.5pt}} \raisebox{1.5pt}{ \color{#404040}\footnotesize \kern{-4pt}\sf\bf{作者提示} }}}\kern{-200pt}.

> 本题输入数据保证小数位数小于 $9$ 位,所以可以使用下面的做法。如果你想知道小数位数不限制的做法,请前往 [B2022](https://www.luogu.com.cn/problem/B2022) 阅读且仅阅读[我个人的题解](https://www.luogu.com.cn/article/ucte2fnf)(因为已有所有题解,除了我的,都是错的)。 你说得对,但是本题目前已有题解全部是错的。讨论区和评论区有不少人提问,但全部被误导,甚至于本题题干都被误导,补充上了错误的提示。因此本人重构本题解来帮助后来人。 首先应当明白,电脑中浮点数都是用二进制存储的,那么一定会存在误差。由于没有分数,所以你可以想象,我们一般常用的十进制也存在着极大地误差(比如 $\frac{1}{3}$ 后面的循环小数 $3$ 会被损失掉)。 那么怎么处理呢?观察到输入数据的小数位数最多只有 $6$ 位,那么第 $7$ 位及后面的位数对答案**理论上讲**不会产生影响。之所以是理论上,是因为上面提到的精度问题,也就是误差。我们可以考虑给我们存储的有误差的浮点数增加一个偏移量 $eps$ 等于在 `double/float` 范围内无限趋近于 $0$ 但不等于 $0$ 的浮点数。这样可以保证: 1. 因为偏移量过小,所以不会对输入的数造成可见(即输出时)的影响。 2. 因为偏移量可以填补掉精度造成的误差,所以数值偏小导致的四舍五入错误就被正确的修复了。 最后还有一个问题:负数的浮点数如何进行四舍五入呢?根据数学知识可知,小于并包含 $4$ 的数舍去为零,大于且包含 $5$ 的数进位变成零。那么对于负数,精度可能造成的误差就不是偏小而是偏大了(如果只考虑绝对值的话,那么还是偏小)。这是就不是增加偏移量而是减小偏移量了。 总结一下,这道题我们需要定义一个无限趋近于零的浮点数 $eps$,在输出时: 1. 若输入值为正数则先**加** $eps$ 再进行四舍五入。 2. 若输入值为负数则先**减** $eps$ 再进行四舍五入。 代码如下: ```c // C #include <stdio.h> #define eps (1e-9) double f; signed main() { scanf("%lf", &f); if (f > 0){ f += eps; } else{ f -= eps; } printf("%.3lf",f); } ``` ```cpp // C++ #include <bits/stdc++.h> using namespace std; const double eps = 1e-9; double f; signed main() { cin >> f; if (f > 0) f += eps; else f -= eps; cout << fixed << setprecision(3) << f; } ``` ```python # Python eps = 1e-9 f = float(input()) # 读取输入并转换为浮点数 if f > 0: f += eps else: f -= eps # 使用格式化字符串控制输出的小数位数 print(f"{f:.3f}") ``` ```java // java import java.util.Scanner; import java.io.PrintWriter; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); PrintWriter out = new PrintWriter(System.out, true); // 使用PrintWriter进行格式化输出,true表示自动flush double f = scanner.nextDouble(); // 读取double类型的输入 // 根据f的值调整,类似于C++中的eps调整 double eps = 1e-9; if (f > 0) { f += eps; } else { f -= eps; } // Java中格式化输出double类型,使用String.format String formattedF = String.format("%.3f", f); // 保留三位小数 out.println(formattedF); // 输出结果 scanner.close(); // 关闭scanner } } ``` ```Pascal // Pascal program Main; const eps = 1e-9; var f: real; formattedString: string; begin // 读取输入 Read(f); // 根据f的正负调整值 if f > 0 then f := f + eps else f := f - eps; // 格式化输出,Pascal没有直接设置小数精度的函数,这里使用String函数格式化 Str(f:0:3, formattedString); // 格式化f为字符串,保留3位小数 WriteLn(formattedString); end. ``` [![](https://jrenc.azurewebsites.net/api/signature?code=zHZRCCItO-yB8t7d2KyitELFDwADnXIotkeeIQL3juyNAzFucnyrWA%3D%3D&name=thanks%20for%20reading%20%20%20%20%20%20%20%20by%20%40wangbinfeng(387009)&animate=true&speed=0.7&color=purple)](https://www.luogu.com.cn/user/387009) $$\color{grey}{\tiny{\texttt{发现上面的签名是动图了吗?}}}$$