深入浅出浮点型

Posted ~(>_<。)\

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出浮点型相关的知识,希望对你有一定的参考价值。

 

惯例在取标题的时候取大了但又不想改┑( ̄Д  ̄)┍


    不知道是不是因为拍黄片太简单了,还是现在国内的培训机构太水了,又或是现在一群天天泡知乎的人自以为随便看看书就能当程序猿了,已经连续几天在php板块看到问为什么PHP的浮点型计算总是不准确,老实说这种基础的问题你在CSDN上,在QQ群里,再不济用会死人的百度一下都应该能知道。但偏偏没事跑囧乎上问,还特一本正经的问为啥解释器不优化一下。。。

    好吧╮(╯-╰)╭ 那今天就稍微聊一下这个话题好了。其实两句话就能解释完,第一计算机计算的时候是使用二进制的,第二数轴是稠密的。

    首先我们知道计算机的计算底层都是基于二进制的,那么我们现在屏蔽掉具体的硬件实现,只谈计算。因为计算机硬件只能识别0和1,所以当我们要计算数字的时候就必须先将其转换为二进制数。一般来说整数转换为二进制应该都会了,就是除二取余然后倒叙输出————如果这个还不会的话请自行百度(后果概不负责<_<)。至于小数的计算,则是乘2取整,顺序输出。什么?你问为什么?总所周知,十进制转二进制公式是2^n+2^(n-1)+……2^0+2^(-1)+2^(-2)……如果你对这个公式没印象,那么恭喜你,你今天打开了新世界的大门╮(╯-╰)╭ 我们求整数部分的转换就是不断除二,直到逼近n,因此小数部分也是如此,不断的乘以0.5,以求和逼近小数m。如果你还记得大学的基础知识的话,应该知道小数部分的极限是1,这也和我们的预期相符合。

    我想一般聪明的朋友应该知道为什么了。对的,因为依据通项公式,你会发现我们很可能只是逼近这个数,但无法达到这个数。比如0.125就是1/8,因此刚好是0.5的三次方,Good luck。但很不凑巧,我们是0.1,那么就囧到了,我们将会得到0.00011001100110011001100110011001……对的,这是一个无限循环小数。然而别忘了我们的电脑不可能无限的计算下去啊,不然光这个转换就可能要耗掉所有CPU和内存,而且即使算到天荒地老,哪怕算到山无陵,天地合依旧没结果。为此我们不得不截取几位,作为可以计算的有效位数。那么要截取几位呢?这时候就需要根据我们具体的类型来判断了,比如float,我们都知道使用32位来存,那是不是意味着就是整数部加小数部正好是32位呢?嗯,恭喜你,你很机智,可惜是错的<_<

    在我们转化之前首先要认识一下浮点数是怎么构成的。以float为例

S PPPPPPPP DDDDDDDDDDDDDDDDDDDDDDD

    其中S为符号位,如果是1则为负数,是0则为正数。P为指数位,也就是说float最多可以表示的范围,D为小数部分。看到这里你就应该知道float的取值范围该怎么求了吧。不要总是傻乎乎的去背浮点数的范围,理解结构要优先于强记。这时你会问为啥是8位不是9位不是7位,嗯,这是个好问题,你可以写封邮件给IEEE,反正我是不知道╮(╯-╰)╭

    好了,既然我们知道了结构,那么就应该动手计算了。

    首先我们需要把这个二进制数转化为科学计数法。比如2.1首先变为10.00011001100110011001100110011001,然后左移一位变成1.00011001100110011001100110011001*2^1,诚如我们已知浮点型的首位表示正负,所以首位是0。接着我们将指数1加上127之后再转化成二进制,变成10000000。可能有人问为啥要加127,这是因为你8位二进制能保存的最小值是-127,所以当你加上127之后可以确保所有的指数都是正数。接着再把小数部分取前23个直接放进去就完成了。so此时我们数据存的将是01000000000011001100110011001100,没错现在我们就看到了底层存储的真实样貌。那么现在请问float的有效可靠位数是多少?答案是6位,想想这是为什么?

    基本上谈到这里聪明的读者应该知道为啥浮点型是不可靠的了。第一我们在保存浮点数的时候就已经是不可靠的了,第二就是我们在前面所提到的,数轴是处处稠密的。

    第一点只是帮我们解释了为什么存是不可靠的,那么第二点就解释了取为啥是不可靠的。因为当我们拿到一个二进制数字的时候并不知道其原来的值是多少,只能通过计算才能获取。那么问题来了,当我拿到数字01000000000011001100110011001100的时候完全无法知道这个究竟是恰好是个这样及精细的小数还是一个一般小数计算后取不尽截取的结果。既然如此,我只能统一作为一个精确的小数来处理了

    至此,我想你也应该知道所谓的浮点数计算不准确,其实和拍黄片本身并没有太大的关系,这是计算机本身的设计问题造成的。不信你可以试试在js或者python里面输入0.56+0.01,结果一定不是0.57。

Tip:如果你用python的print方法输出的话会发现是0.57,而原值不是,猜猜这是为什么?

    也真因为如此,所以基于浮点数的普通计算也都是不可靠的。当然解决方法也是有不少的,一个比较通用且可行的方法就是将浮点数运算变成整数运算。简单说,就是当你对精度要求较高的情况下,可以适当的放大浮点型数值为整数,在所有运算结束之后,只需要显示的时候缩到原来的比率即可。这可以保证最大限度在计算时不丢失精度。所以现在你该理解为啥支付系统默认的金额单位都是一分了吧。当然也还有其他方法,这里就不一一说明了(其实是我不知道这种事会到处乱说)


    基本上能说的就这么多,虽然这东西挺基础的,但不好好复习也不行哦。为此留一道练习题给各位吧:

    已知double型使用11位表示指数位,那么求1.23用double型储存时的二进制数为多少。

以上是关于深入浅出浮点型的主要内容,如果未能解决你的问题,请参考以下文章

深入C#数据类型

深入理解Java虚拟机——运算指令

深入理解Java虚拟机——运算指令

java基础总结--深入理解基本数据类型

深入理解计算机系统(2.7)------二进制小数和IEEE浮点标准

深入理解计算机系统(2.7)------二进制小数和IEEE浮点标准