题解:B2022 输出保留 12 位小数的浮点数
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}.
> 本题的本意想考察各种语言输出时控制小数位数,但是这种做法本来就是错误的,且目前已有的题解也全部是严重错误的,因此我重构了题解。不过在这篇题解中,我想讲一下题目的本意,再讲正确的做法。首先你应当会控制输出时的小数位数,具体可以前往我写的 [B2021](https://www.luogu.com.cn/problem/B2021) 题的[题解](https://www.luogu.com.cn/article/rec0f3xq)查看题目本意要求的做法(请注意,你只应当查看我写的题解,而暂时不要阅读其它题解,因为全部是错误的,具体理由可以看[这里](https://www.luogu.com.cn/discuss/919282)。
我们考虑到将 B2021 的代码直接带到这里,并且修改以下内容即可 AC 本题 ~~(目前题目数据是错误的,如果你 WA 且仅 WA 掉了 #4,那么恭喜你做对了本题)~~:
1. 将 `float` 修改为 `long double`;
2. 将 $eps$ 从 $1\times10^{-9}$ 改为 $1\times10^{-15}$;
3. 将输出时的格式符改掉,具体地,对于 C 语言为 `printf("%.12Lf", f)`,
对于 C++ 语言为 `cout << fixed << setprecision(12) << f`,
对于 Python 语言为 `print(f"{f:.12f}")`,
对于 java 语言为 `String formattedF = String.format("%.12f", f)`,
对于 Pascal 语言为 `Str(f:0:12, formattedString)`。
咋一看这么做好像挺对的,毕竟考虑过浮点数的误差了。但是考虑这个数据:`0.499999999...`(暂时保留一位小数),数学上的答案显然应该是 `0.4`,但是由于误差和 $eps$ 的原因很可能被代码误认为是 `0.5`。那么有没有办法处理这种数据呢?答案是有的。因为输入的数据是十进制,那么保证了十进制数是不存在误差的。那么误差的来源就是进制转换,即我们不能在代码中有进制转换出现。
那么,`float/double/long double` 等自带的浮点数显然就不能用了,我们只能自己写一个浮点数处理的程序。存储输入的浮点数我们可以采用一个数组来存储每一位的数值(我的代码中使用了字符串,实际上它们的作用是相同的),然后对这个数组进行操作。
全部的流程如下,读者可以把本题当成一个普通的模拟题来完成:
1. 如果输入的是负数,可以把负号提取出来,然后就不存在负数了。
2. 如果输入的是整数,可以考虑直接输出,也可以考虑用第 3. 步的方法。
3. 若输入的小数位数不足 $12$ 位,则补足 $12$ 位(当然也可以补充更多的位数,比如为了方便判断四舍五入而补足 $13$ 位)。
4. 判断小数部分第 $13$ 位的数值,如果大于等于 $5$ 则向上进一位,否则不对上一位产生影响。
5. 若第 $12$ 位进位变成了 $10$,但由于十进制中没有 $10$ 这个数字(数字只单独一位的数值),将这一位补零并向上一位进位。
6. 重复第 5. 步直到没有任何一位存在 $10$。
7. 注意小数部分最高位进位需要进到整数部分,而整数部分最高位如果进位会导致结果答案变长。注意特判即可。
这道题可以练习大家的代码功底,作者本人暂且只提供 C++ 语言的代码,其他语言请大家自行练习。
```cpp
#include<bits/stdc++.h>
using namespace std;
string s;
int i;
bool flag_zero,flag_fu,flag_jw;
signed main(){
cin>>s;
for(i=0;s[i];i++)
if(s[i]=='.')break;
if(i==s.length()){
cout<<s<<'.';
for(int i=1;i<=12;i++)cout<<'0';
return 0;
}
if(s[0]=='0')flag_zero=true;
if(s[0]=='-')s[0]='0',flag_fu=true;
for(int j=1;j<=14;j++)s+='0';
if(s[i+13]>='5')s[i+12]++;
for(int j=i+12;j>i;j--)if(s[j]>'9')s[j]='0',s[j-1]++;
if(s[i]>'.')s[i]='.',s[i-1]++;
for(;i>=0;i--)if(s[i]>'9'){
if(i==0)flag_jw=true;
else s[i-1]++;
s[i]='0';
}
if(flag_fu)cout<<'-';
if(flag_jw)cout<<"10";
else if(flag_zero)cout<<'0';
for(i=(s[0]=='0');s[i]!='.';i++)cout<<s[i];
cout<<'.';
for(int j=1;j<=12;j++)cout<<s[i+j];
}
```
- 鸣谢:
+ 1. [@wangbinfeng](https://www.luogu.com.cn/user/387009) 首先确定本题数据点的错误位置及原因,两次重构本题解(这是第三版题解)。
2. [@N_z_](https://www.luogu.com.cn/user/320087) 指出上一版题解的错误并且给出了本题解使用的正确的做法的思路。
3. 阅读我的题解的大家。
[&animate=true&speed=0.7&color=purple)](https://www.luogu.com.cn/user/387009)
$$\color{grey}{\tiny{\texttt{发现上面的签名是动图了吗?}}}$$