题解:B2021 输出保留 3 位小数的浮点数
wangbinfeng
·
·
题解
[
\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.
```
[&animate=true&speed=0.7&color=purple)](https://www.luogu.com.cn/user/387009)
$$\color{grey}{\tiny{\texttt{发现上面的签名是动图了吗?}}}$$