您的位置:首页 > 其它

习题3-12 浮点数(Floating-Point Numbers, UVa11809)

2017-07-30 19:15 519 查看

题目

计算机常用阶码-尾数的方法保存浮点数。如图3-9所示,如果阶码有6位,尾数有8位, 可以表达的最大浮点数为0.1111111112×21111112。注意小数点后第一位必须为1,所以一共有9 位小数。



图3-9 阶码-尾数保存浮点数

这个数换算成十进制之后就是0.998046875×263=9.205357638345294×1018。你的任务是根据这个最大浮点数,求出阶码的位数E和尾数的位数M。输入格式为AeB,表示最大浮点数为A×10B。0<A<10,并且恰好包含15位有效数字。输入结束标志为0e0。对于每组数据,输出M和E。输入保证有唯一解,且0≤M≤9,1≤E≤30。在本题中,M+E+2不必为8的整数倍。

分析

如果对于每一个数据都进行一次计算,运算量会很大。而鉴于0≤M≤9,1≤E≤30,打表是一个很不错的方法。

题目的意思就是让程序通过特定位数的阶码和尾数所能保存的最大的浮点数来找到阶码和尾数的位数,即某M值和E值决定了最大浮点数,然后要通过这个最大浮点数反推M值和E值。然而我刚读完题的时候完全不知道题在说什么,看来我的语文和数学都白学了(怎么感觉这句话有歧义呢\(ಡωಡ)/)。

设二进制表达式的尾数(mantissa)为m,指数为e,十进制的尾数为a,指数为b(不懂尾数的自行度娘,虽然我最开始也不懂\(ಡωಡ)/),则有m×2e=a×10b(原式)。

m的值为0.1111……,即m=2−1+2−2+2−3+……+2−1−M。e的值为2E−1。

由于1≤E≤30,直接计算e的值的话会溢出,所以需要将原式两边同时取对数,得到lg(m)+elg(2)=lg(a)+b(2式)。因为2式左边必为小数,而b必为整数,所以lg(a)是一个小数。

那么我们要做的就是先循环M和E值得到对应的a值和b值,并储存在数组中,然后每次读入数据,找到对应的a值和b值,最后输出。

技巧

这一道题涉及四个技巧。

1. m和e的二进值求值(这个只提一下,因为确实没有什么好说的,上面的分析部分已经把怎么用给解释了)。

2. 将double和float型的数据转化为int时,小数部分会被舍弃。

3. 分离科学记数法表达的字符串的时候(例如:1.15949849494156e18),可以将”e”替换成” “(空格),然后用stringstream分离。

4. 比较两个浮点数的时候,不能直接比较。下面提供两种方法:

1):用两个浮点数的差与某一个较小数比较(通常为double的精度,这里使用我代码中用到的1e-5)

...
#include<cmath>
#define EPSINON 1e-5
...
void compare_double(double a, double b){
if(fabs(a - b) < EPSINON) cout<<“相等”;       //fabs()区别于abs(),适用于浮点型
else cout<<"不相等";
}


2)比较相对差值(适用于整数部分比较大的浮点型,例如:1234567890.123456)

...
#include<cmath>
#define EPSINON 1e-16
...
void compare_double(double a, double b){
if(fabs(a - b) < EPSINON * max(a, b)) cout<<"相等";
else cout<<"不相等";
}


实现代码

#include<iostream>
#include<cstring>
#include<sstream>
#include<cmath>
//#define EPSINON 1e-16
using namespace std;

int main(){
double tableA[20][40];        //保存A值
int tableB[22][40];     //保存B值
int b;
double mantissa, exponent, a, temp;
for(int m = 0; m < 10; m++)
for(int e = 1; e < 31; e++){
mantissa = 1 - pow(2, -1-m);
exponent = pow(2, e) - 1;
temp = log10(mantissa) + exponent * log10(2);
tableB[m][e] = temp;
tableA[m][e] = pow(10, temp - tableB[m][e]);  //此时的temp和tableB[m][e]不是同一个值(技巧2)
}
string s;
while(cin >> s && s != "0e0"){
s.replace(s.find('e'), 1, " ");
stringstream ss(s);
ss >> a >> b;
for(int m = 0; m < 10; m++)
for(int e = 1; e < 31; e++)
/*if(b == tableB[m][e] && fabs(a - tableA[m][e]) < EPSINON * max(a, tableA[m][e]))
cout<<m<<" "<<e<<endl;
*/
if(b == tableB[m][e] && fabs(a - tableA[m][e]) < 1e-5)
cout<<m<<" "<<e<<endl;
}
}


总结

其实这一道题花了我整整半天,一开始完全没有头绪,又苦于对进制方面不是很熟,去参看了很多大佬的博客后才明白这一道题该如何解。

这几道题有两个难关把我卡住了,第一个是如何解决e值的溢出,第二个是如何比较两个浮点数。

e值的溢出我看了几个大佬的分析就解决了,但是比较浮点数确实麻烦。我看大佬的博客再码代码,一个多小时就解决了,但是到UVa上面一直WA。然后调试了整整半天,终于搞清楚了是怎么一回事。

我一开始写的代码是比较相对差值(即上面注释掉的部分),但是这个地方的double却只能保存6位有效数字,而这道题里整数部分一定只有一个数字,所以小数部分只能存5个数字,所导致的结果就是例如这样的数据9.205357638345294e18,变成double后就成了9.20536(这根本就是两个不同的数好不好!!\(ಡωಡ)/),所以精度太高就没法求解(好不容易觉得AP学了一些有用的东西,结果……\(ಡωಡ)/)。不过这一个bug也给我了很好的启发——今后写代码的时候EPSINON要根据程序需要,灵活使用,不要学的太死;比如这一道题,如果把EPSINON定义为double的精度就不行,因为整数部分始终有一个数字,所以只能把EPSINON定义为1e-5。

总而言之,这一道题还是挺有意义的,没事多看几遍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: