计算机计算“小数”出错的原因?

本文深入解析计算机内部如何表示二进制小数,解释为何某些十进制小数在二进制中无法精确表示,如0.1。通过探讨double和float类型,了解指数和尾数的概念,以及正则表达式和EXCESS表示法。最后,通过实践例题,演示如何用单精度浮点数表示特定的十进制数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、问提提出:

在EditPlus中用Java语言,for循环加0.1一百次:
在这里插入图片描述
sum等于100吗,让我们在cmd编译运行看看结果:
在这里插入图片描述


二、为什么会这样?

看来需要深入了解一下计算机内部是怎样的过程了。
计算机所有信息都是二进制01代码,小数也不例外,
那么小数是怎么用二进制表示的呢?
二进制0.1表示十进制0.5
二进制0.01表示十进制0.25
二进制0.001表示十进制0.125
二进制0.0001表示十进制0.0625
这是根据2的(-1),2的(-2),2的(-3),2的(-4)次方得出来的

  • 以下是四位二进制数的表:
    在这里插入图片描述
    可见,有些十进制小数是不能用有限位数的二进制小数表示的
    (有的朋友可能没理解这句话,随便举个栗子,就拿前两行说,
    二进制0.01(十进制0.25)和二进制0.1(十进制0.5)之间的十进制数
    就无法用4位二进制数表示)
    当然,如果再增加1位二进制小数,所对应的十进制数也会增加几个,
    但是,有些数,不管增加多少位二进制数,都不能表示十进制数,
    比如说:0.1,我们用电脑来转换一下看看:
    在这里插入图片描述
    0.00011 0011 0011无限循环,一直逼近
    这就跟1/3是0.333333循环一样,不能有限表示。

说到这里,我们的开头提出的问题就真相大白了,
根本原因就在于,计算机无法精确表示0.1这个十进制数。


三、计算机到底是怎样表示二进制小数的?

1011.0011这样的二进制小数,是我们在纸面上写的,在计算机里都是高低电平,磁极的正负,不会出现小数点的,所以,计算机到底是怎样表示二进制小数的呢?
那就是double和float!

一个小数,我们可以写成如下形式
在这里插入图片描述
这里,基数是2,所以可以省略
符号位表示正负,占一位
指数和尾数部分我们稍后详细说明

双精度用64位比特二进制,分别来表示符号、尾数、基数和指数,而单精度用32位。
在这里插入图片描述


四、详细说明一下指数和尾数

尾数部分是“将小数点前面的值固定为1的正则表达式”
指数部分是“用EXCESS表示法表示的数”

1.正则表达式:

相信中学阶段我们就学过科学计数法了,规定前面的数必须是1.3。
在这里插入图片描述

正则表达式其实大同小异,现在,我们用二进制来举个栗子:
尾数部分是“将小数点前面的值固定为1的正则表达式”
比如:用单精度表示1011.0011
在这里插入图片描述
可以想到,尾数我们将小数点向左移动了3位,那指数部分岂不就是3?
半对半错,哈哈,为什么呢?因为,为了让指数部分没有负数,我们用EXCESS重新规定了一下。

2.EXCESS:

在这里插入图片描述
其实,就是单精度指数部分是8位,能表示2^8个数,即256,即0-255
我们用一半,即255/2=127表示0,相当于可以用正数表示-127-128了。


五、亲手试一试并在程序中确认:

学完了理论,让我们在实践中检验一下!
例题:用单精度浮点数表示十进制数-0.75。
在这里插入图片描述
在这里插入图片描述
符号位:1
尾数:0.75 —> 0.25+0.5 —> 0.11 —> 1.1 指数-1
所以为:1后面22个0
指数:-1用EXCESS 126表示,换成2进制,0111 1110
所以,最终结果为:1 0111 1110 1后面22个0

①http://www.binaryconvert.com/result_float.html?decimal=048046055053
这个网站在线验证:
在这里插入图片描述
②C语言验证:
在这里插入图片描述在这里插入图片描述
②java语言验证:

import java.text.DecimalFormat;


public class SinglePrecision {

//浮点到二进制
    public String Float2Binary(double n) {//使用double输入,不影响
        int signBit;//符号位
//        String s = Float.toString((float) n);  这个方法会自动用 科学记数法4.52E-4
        String s = (new DecimalFormat("################.######")).format(n);

        /**
         * 处理符号位
         * 本来是有正负0的,这个地方就默认是正0了
         */
        if (n == 0) {
            signBit = 0;
        } else if (n < 0) {
            signBit = 1;
            s = s.substring(1);//把负号去掉
        } else {
            signBit = 0;
        }

        /**
         * 不用判断是小数还是整数,Float.toString()会自动在整数后补0,0也一样
         * 将整数部分小数部分分开:
         */
        String intFloat[] = s.split("\\.");
        int intPart = Integer.valueOf(intFloat[0]);
        float floatPart = Float.valueOf("0." + intFloat[1]);
//        System.out.println(s);
//        System.out.println(intPart);
        //处理整数部分
        boolean isReady = false;
        String str_intpart = "";   //以1开头的二进制数 如10——>1010,而不是32位
        for (int i = 31; i >= 0; i--) {  // 
            if (((intPart >> i) & 1) == 1) {
                isReady = true;
            }
            if (isReady) {
                str_intpart += ((intPart >> i) & 1) == 1 ? 1 : 0;
            }
        }

        //处理小数部分——乘2取整法
        String str_floartPart = "";
        while (floatPart != 0) {
            floatPart *= 2;
            if (floatPart >= 1) {
                floatPart -= 1;
                str_floartPart += 1;
            } else str_floartPart += 0;
        }

        /**
         * 规格化
         */
        String str_int_float = str_intpart + str_floartPart+"0000000000000000000000000000000"; //补0是因为可能不够长
        //现在得到二进制下的:"整数(.)小数"格式。考虑规格化
        int diff = 0;//  规格化所要将小数部分移动的位数,主要是针对整数部分为0的
        int intlen = str_intpart.length();
        str_intpart = str_int_float.substring(0, 1);
        str_floartPart = str_int_float.substring(1,23);
        while (str_intpart.charAt(0) != '1') {
            str_intpart = str_floartPart.substring(0, 1);
            str_floartPart = str_floartPart.substring(1);
            diff++;
        }

        int expChange = intlen != 0 ? intlen - 1 : -diff - 1;  //规格化所带来的指数的移动

        //得到8位指数部分;
        int exponential = expChange + 127;//真正的指数
        String exp = "";  //指数
        for (int i = 7; i >= 0; i--) {
            exp += ((exponential >> i) & 1) == 1 ? 1 : 0;
        }

        //得到23位底数部分
        String base = str_floartPart;
        int len = base.length();
        for (int i = 0; i < 23 - len; i++) {
            base += 0;
        }

        return signBit + " " + exp + " " + base;
    }

   //二进制到浮点小数
    public double binary2Float (String binary){
        if (binary.length()!=32) {
            System.out.println("Error!");
            return 0;
        }
        String exp = binary.substring(1,9);
        String base = "1"+binary.substring(9);
        /**
         * 分类:
         * 底数全是0
         */
        double Base = 0;
        int Exp = Integer.valueOf(exp,2)-127;
        for (int i =0;i<24;i++){
            Base+=(base.charAt(i)=='1'?Math.pow(2,-i):0);
        }
        return Base*Math.pow(2,Exp);
    }

    public static void main(String[] args) {
        SinglePrecision sp = new SinglePrecision();
        System.out.println(sp.Float2Binary(256.9375));
        System.out.println(sp.binary2Float("01000011100000000111100000000000"));

        /**
         * output:
         * 0 10000111 00000000111100000000000
         * 256.9375
         */

    }
}

//目前精度还是不够,误差较大。

六、有什么解决办法吗:

①对于精度不高的,忽略就行,反正误差也不大
②对于精度高的,Java有BigDecimal类
③化成整数,比如要算100个0.1相加,可以算100个1相加,再除以10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值