double型最大正值和最小正值探秘

文章讲述了如何在算法竞赛中计算double型浮点数的最大正数值和最小正数值,通过代码示例、头文件宏和半自动方法进行了详细解析。作者强调了浮点数主要的问题是误差而非数值超限。
摘要由CSDN通过智能技术生成

【题目来源】

刘汝佳《算法竞赛入门经典  第2版》第一章习题——问题3:double型浮点数最大正数值和最小正数值分别是多少(不必特别精确)?

书中并未给出标准答案,这道题应该怎么解呢?老金还着实费了不少脑细胞。

一、跑到海枯石烂的代码

网上查到这样的代码:

#include <stdio.h>
int main(){
    double i=0.0000000000000001;
    for(;i>0;i+=0.0000000000000001){
    }
    printf("%.100lf\n%.100lf\n",i,i-1);
    return 0;
}

实际测试了一下,运行到海枯石烂也没等到结果。如果有人等到结果还望告知。

从代码看,其逻辑应该是数值超限时数值的符号会变号,由正数变成负数。

鉴于有这么虐人的运行时间,估计出题者的标准答案肯定不是如此。

二、利用头文件宏获取

在float.h头文件中,定义了两个宏:

DBL_MAX:double类型能够表示的最大有限正数。

DBL_MIN:double类型能够表示的最小正规化正数值(可以理解为比最小正数大,但接近于最小正数的值)。

#include <stdio.h>
#include <float.h>
int main() {
    printf("最大正数值: %g\n", DBL_MAX);
    printf("最小正规化正数值: %g\n", DBL_MIN);
    return 0;
}

结果输出:

最大正数值: 1.79769e+308

最小正规化正数值: 2.22507e-308

这应该是比较靠谱的数据了,这个值真的是很大很大,所以在实际编程时,完全没有必要考虑数值超限的问题。

因此,浮点数的主要问题是误差,而不是数值超限。

三、老金的半自动步枪

老金经过尝试,可以用一种“编程+人工确认”的半自动输出方法。虽然是半自动方法,但是非常简单高效。

因为要求最大正数和最小正数,数字肯定会非常大,所以要用到科学记数法表示。科学记数法表示的数分为基数部分和指数部分,只要分别确定这两部分,这个数也就确定了。

1. 求最大正数值

 (1) 求最大正数值的指数部分

思路是将基数设为1,再将指数从0进行累加,然后人工确认输出结果出现异常的地方,就是指数的最大值。

#include<stdio.h>
int main(){
    //求最大正数值的指数部分
    double max=1e0;
    for(int i=1;i<=400;i++){
        max*=1e1;
        printf("%d %e\n",i, max);
    }
    return 0;
}

代码中的循环条件“i<=400”根据实际测试结果给的,这时输出已经出现异常。

根据下图输出结果,可知最大指数为308。

上面的“1.#INF00e+000”是什么东东呢?这其实也是一种科学记数法的表示形式,只不过指部为0,而基数的前半部分“1.#INF”表示“无穷大inf (infinity 的缩写)”,所以“1.#INF00e+000”自然也表示无穷大。但这只是对“无穷大”的数的一种标记方式,它本身并不是一个具体的值,也就不能和其他的值作比较,所以这里很难通过程序实现自动判断数值超限位置。

(2) 求最大正数值的基数部分

上面已经求出最大指数是308,下面就将指数设为308,再将基数从1进行累加,然后人工确认输出结果出现异常的地方,就是基数的最大值。

#include<stdio.h>
int main(){
    //求最大正数值的基数部分
    double max=1e308;
    for(int i=1;i<=1000;i++){
        max+=0.01e308;
        printf("%d %e\n",i, max);
    }
    return 0;
}

根据下图输出结果,可知最大基数为1.79,所以double型浮点数最大正数值为1.79e+308,与前面用宏给出的最大值1.79769e+308一致(只不过咱们的精度没有它那么高)。

2. 求最小正数值

(1) 求最小正数值的指数部分

思路是将基数设为1,再将指数从0进行累减,然后人工确认输出结果出现异常的地方,就是指数的最小值。

#include<stdio.h>
int main(){
    //求最小正数值的指数
    double min=1e0;
    for(int i=1;i<=400;i++){
        min*=1e-1;
        printf("%d %e\n",i, min);
    }
    return 0;
}

根据下图输出结果,可知最小指数为-324

(2) 求最小正数值的基数部分

上面已经求出最小指数是324,下面就将指数设为324,再将基数从9进行累减,然后人工确认输出结果出现异常的地方,就是基数的最小值。

但是这时候已经不能靠代码进行累减了,因为9e-324本来已经小到接近极限了,没法再让程序减去一个固定的最小值了,所以此时就要人工实现累减。方法也很简单,就是将下面代码中的基数9依次改为8、7、6、5、4、3、2、1后逐一运行程序。

#include<stdio.h>
int main(){
    //求最小正数值的基数
    double min=9e-324;
    printf("%e\n", min);
    return 0;
}

输出结果如下:

9e-324输出9.881313e-324

8e-324输出9.881313e-324

7e-324输出4.940656e-324

6e-324输出4.940656e-324

5e-324输出4.940656e-324

4e-324输出4.940656e-324

3e-324输出4.940656e-324

2e-324输出0.000000e+000

1e-324输出0.000000e+000

可见,当改为7e-324以下、3e-324以上时,程序一律将值视为4.940656e-324,当改到2e-324时程序已经将它视为0。

所以double型浮点数最小正数值为4.940656e-324,前面由宏输出的结果为2.22507e-308,咱们得到的值比它这个值还要小很多。

综上,老金这种半自动化的方法虽然不那么先进,但却是三种方法中最简单高效、最接近标准答案的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

金创想

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

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

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

打赏作者

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

抵扣说明:

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

余额充值