【double浮点数精度 四舍五入问题】 算法笔记 Codeup 1918 简单计算器

21 篇文章 1 订阅

【double浮点数精度 四舍五入问题】 算法笔记 Codeup 1918 简单计算器

题目本身难度不大,但是发现了一个非常关键的问题,就是浮点数四舍五入的时候出现的问题。

题目本身并不是本文要讨论的重点,这里就不放题干啦。

众所周知,对于表达式计算的题目,其实如果采用Python当中的eval函数,一行就能搞定啦:

while(True):
    temp=input()
    if(temp=="0"): break
    print(format(eval(temp),'.2f'))

当然,这道题的正解显然是栈的应用,用C++中的stack来实现,这也是《算法笔记》书中的例题。

下面讲述我遇到的一个问题

偶然发现,在书中给定的Codeup练习平台上,其实存在两道名称相同,题目内容描述都一模一样的题目。然而,我的C++代码在一道题目AC,而另一道题目却WA,这是怎么回事呢,让我百思不得其解。

在这里插入图片描述

于是,我在TK题库花费1元巨资下载了题目的测试数据(相同内容的题目,一道AC一道WA,当然是下载WA的这道的数据啦),其中测试数据的文本文件里共有1000行输入,我通过采用freopen的方式将test0.in文件作为输入,进行本地测试,将输出结果和test0.out文件的内容进行对比。很显然肉眼是发现不了问题的。

于是我将程序的输出存到另一个文件test1.out中,通过下面这个程序,找出这个文件和给定的测试数据的输出文件进行逐行对比,找到了问题所在:

#include <iostream>
#include <fstream>
#include <string>
#include <cstdio>
using namespace std;
int main()
{
	ifstream fin0("test0.out");
	ifstream fin1("test1.out");
	string a,b;
	for(int i=1;i<=1000;i++) 
	{
		getline(fin0,a);
		getline(fin1,b);
		if(a!=b) cout<<"不同:"<<i<<endl;
	}
	return 0;
}

输出显示,在第378行出现不同。

测试数据中第378行的输入内容是

81 * 3 * 29 - 49 - 0 * 2 + 29 - 65 + 2 - 42 * 81 * 40 - 57 / 30 - 67 / 40 - 96 + 35 * 6 * 18 + 31 - 30 * 28 - 49 - 78

我们通过简单计算,即可得到精确的答案是

-126371.575

题目要求是精确到小数点后2位

我的的程序输出是

-126371.58

但我发现,题目测试数据给的输出是

-126371.57

讲道理0.575四舍五入不应该就是0.58嘛,可能出题人默认要求的就是采用这种舍入方法吧,因为它是个负数。

事实上,在C++当中,当设置保留的位数后面恰好只有一位并且是5的时候,有时会出现不同的结果:

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
	double a=0.55,b=1.55,c=2.55,d=3.55;
	printf("%.1f\n%.1f\n%.1f\n%.1f\n",a,b,c,d);
	return 0;
}

例如,运行上面这段程序,将输出

0.6
1.6
2.5
3.5

这其实跟计算机存储数的方式有关系,如果一定要追究原理,那么可以参考这篇文章

由于0.575本身就并不能用二进制数准确表示,就导致了问题的出现。

当我把程序最终输出改为 %.30f 时,我的程序输出

-126371.575000000011641532182693481445

而网上下载的其他人AC代码的程序输出

-126371.574999999997089616954326629639

到这里,我大概知道了问题所在。计算过程的不同,就有可能导致最终的结果不同。

虽然我写的程序并没有错,但我们发现了问题,也就应该尽可能去避免,万一考试或竞赛当中正好就遇上了呢。保持细致严谨的态度总是好的。

其实,如果是在计算几何题目当中,这样的问题是很常见的,我们一般是引入一个极小数eps来对这种误差进行修正(《算法笔记》第75页也有讲述)。对于这道题,我们也是采用同样的办法。

也可以看这篇参考资料:计算几何中的精度问题

本人遇到的问题其实就和文中的这个案例相似:

在这里插入图片描述

最终,我们在代码当中稍作修改,对于计算结果是负数的情况,我们先对结果加上一个极小数eps,再保留2位小数,就成功解决问题啦。

另外,对于这道题来说,因为只涉及到加减乘除四种运算,而没有涉及到其他运算符号或者括号,我们采用较为简单的方法就能解决了。书上给出的参考代码可以解决更加普遍的问题。

AC代码:

#include <iostream>
#include <iomanip>
#include <cctype>
#include <cstdio>
#include <string>
#include <stack>
#include <sstream>
using namespace std;
const double eps=1e-8; 
int main()
{
	//freopen("test0.in","r",stdin);
	//freopen("test1.out","w",stdout);
	stringstream ss;
	string str;
	while(getline(cin,str))
	{
		if(str=="0") break;
		ss.clear();
		ss<<str;
		stack<double> s;
		double temp,ans=0;
		int n;
		char t;
		ss>>n;
		//cout<<ss.rdbuf()->in_avail()<<endl;
		s.push(n);
		while(ss>>t)
		{
			ss>>n;
			if(t=='*')
			{
				temp=s.top();
				s.pop();
				s.push(temp*n);
			}
			else if(t=='/')
			{
				temp=s.top();
				s.pop();
				s.push(temp/n);
			}
			else if(t=='+')
				s.push(n);
			else if(t=='-')
				s.push(-n);
		}
		while(s.size()>0)
		{
			//cout<<s.top()<<endl;
			ans+=s.top();
			s.pop();
		}
		printf("%.2f\n",ans>0?ans:ans+eps);//对于负数,加上一个极小数eps,成功解决问题 
		//cout<<fixed<<setprecision(2)<<ans<<endl;
	}
	return 0;
}
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

球王武磊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值