计算机小数运算出错的原因
计算机所有信息的表现形式都是二进制
小数运算出错的栗子:
#include <stdio.h>
void main(){
int i;
float sum=0;
for(i=0;i<100;i++){
sum += 0.1;
}
printf("%f",sum);
}
输出结果不是10,而是10.000002。
这是因为在计算机内部,小数的表现方法也是二进制。这里可以回顾上一篇里对二进制的总结,可以发现计算机进行从二进制到十进制的换算,使用的是因数x位权相累加的结果。然而这种位权是离散的!因此,
有一些十进制小数无法用二进制表示
例如,二进制的小数点后4位,转换成十进制时,小数部分只能是0.5、0.25、0.125、0.0625四个数的相互叠加,他们是有上下界的离散数字。

这就造成了,像0.1这样的十进制数,在计算机内部无法用二进制精确表示,只能被表示成无限循环小数,最后被截断近似或四舍五入近似处理,造成了运算出错。
计算机内部的小数表现形式:浮点数
浮点数构成



IEEE:

尾数部分使用正则表达式:统一化浮点数表现样式
正则表达式:按照特定的规则来表示数据的形式,就是正则表达式。
注意:这里的正则表达式只是小数的正则表达式,与字符串和数据库中的正则表达式无关。
规则
十进制:小数点前为全为0,小数点后第一位不可为0
二进制:将小数点前的一个位的值固定为1。(通过逻辑移位)

指数部分使用EXCESS系统:不使用符号位即表示负数


实际程序栗子
#include <stdio.h>
#include <string.h>
void main(){
float data;
unsigned long buff;
int i;
char s[34];
//将0.75以单精度浮点数的形式存储在变量data中
data = (float)0.75;
//将数据复制到4字节长度的整数变量buff中,以数个提取出每一个位
memcpy(&buff,&data,4);
//逐一提取出每一位
for(i = 33; i >= 0; i--){
if(i == 1 || i == 10){
//加入破折号以区分符号部分,指数部分和尾数部分
s[i] = '-';
}else{
//为每个字节复制'0'或者'1'
if(buff % 2 == 1){
s[i] = '1';
}else{
s[i] = '0';
}
buff /= 2;
}
}
s[34] = '\0';
//显示结果
printf("%s\n", s);
}
结果




如果使用同样的程序,将0.75改为0.1,可以出现本文开头提到的错误运算。

对于0.1,可以看到,符号位为0(正数),指数部分01111011的十进制为123,EXCESS系统表示为123-127=-4。尾数部分为无限循环小数。。。。。。这里使用截断近似取前4位,尾数部分变成1.1001......=1.5625.整个数字用十进制表示为1.5625x2^(-4)=0.09765625......不等于0.1。
怎样回避出错
途径一:确定精度,适当近似
途径二:先放大为正数,再缩小回小数

16进制表示小数


网友评论