Double类型数据造成的数据精度丢失

本文最后更新于 2024年6月7日 下午

这是由一道简单的蓝桥杯例题所得出的小经验,这道题目的主要内容是取一个数小数点后的某几位数字。

首先我想到,要取小数点后的n位数字,只要我将数小数点后的数字其乘以10的n次方,得到的整数部分不就是所求的数字吗?

于是我使用了这样的方式在 C++ 中计算:

1
long long a = long long ( a * pow ( 10 , n ) );

但最后验证结果却显示我的结果不正确,我与一份正确的代码进行对比,发现除了求阶乘部分不一样外,我们的代码是完全一样的。那么完全正确的代码中是如何求阶乘的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define ll long long


ll Qpow( ll a, ll b )
# a为底数 ,b为指数
{
ll res = 1;
while ( b )
# b不为0
{
if ( b & 1 ) res = res * a;
#b为1的情况下为最后一次运算,用res保存结果
a = a * a;
#求a的平方
b >>= 1;
#b除2,当b为1时除2结果为0,下一次跳出循环
}
return res;
}

那么为什么会出现,两种不同的求阶乘方式,最后的结果却不一样的现象呢?


具体的原因还要和浮点数在计算机中的存储方式有关,我们知道计算机通用以IEEE754标准存储浮点数,浮点数分为“单精度”与“双精度”两种。计算机也将我们人类世界的浮点数转化为这以两种形式表示。

浮点数 n 表示为:

1
n = ( -1 )^S * M * 2 ^ E

其中 S 、M 、E都为二进制数

S为符号,M为尾数,E为阶码

双精度中S为1位,E为11位,M为52位
单精度中S为1位,E为8位,M为23位

浮点数的存储结构如下:
490pxGeneralfloatingpointfrac.svg.png

由低到高分别为符号、阶码、尾数,这是一种类似于科学计数法的表示方式,我们知道计算机中的数都是以二进制进行保存的,所以将复数转化为这种形式,便于存储于计算机中。

但这样也是有局限性的,因为计算机的存储数字位数有限,所以这种数字表示的离散的,但显示世界中的数字是连续的,这就造成并不是所有的数字都可以表示在计算机中,这就造成了浮点数表示的误差。

举个例子来说

1
123456789123456789123456789

这个数转换为科学计数法为

1
1.23456789123456789123457 * 10 ^ {26} 

可以发现,如果我小数点后只能容纳23位,但转换为科学计数法后却有26位,没办法只能将最后的数字舍掉了。这就造成了误差。


OK!让我们再来看一下pow函数原型

1
2
3
4
5
6
double pow (double base , double exponent);

float pow (float base , float exponent);

long double pow (long double base, long double exponent);

可以看到pow函数的形式参数的类型为double,所以在传入任何类型的实参都会将其转化为double类型,也就是说传参的过程中一旦参数过大一定会出现精度丢失的情况。这就是为什么pow函数精度丢失的原因。


Double类型数据造成的数据精度丢失
https://siegelion.cn/2020/01/20/Double类型数据造成的数据精度丢失/
作者
siegelion
发布于
2020年1月20日
许可协议