代码之谜(五)- 浮点数(谁偷了你的精度?)
博客分类: 代码之谜
代码之谜codepuzzle浮点数 .
****光棍节加长版****
如果我告诉你,中关村配置最高的电子计算机的计算精度还不如一个便利店卖的手持计算器,你一定会反驳我:「今天写博客之前又忘记吃药了吧」。
你可以用最主流的编程语言计算 0.2 + 0.4,如果你使用的是 Chrome、FireFox、IE 8+,可以按 F12 键,然后找到 「控制台」,输入上面的 表达式 0.2 + 0.4,回车。
然后再用最简陋的计算器(如果你没有手持计算器没关系,手机、电脑都自带一个计算器,打开“运行”,输入calc,回车) 再计算一下刚才的 算式 0.2 + 0.4。
怎么样?同意我的观点了吧! 再简陋的计算器也比超级计算器的精度高,关键不在于它的频率和内存,而在于它是如何设计、如何表示、如何计算的。
不能表示 VS 不能精确表示
在上一章『浮点数(从惊讶到思考)』(iteye地址:http://justjavac.iteye.com/blog/1725977)中我们讲到用浮点数表示 数 时出现的问题——很多数都 不能表示。(注意 浮点数表示的是数,而不仅仅是小数。)
如果你数学比较好,或者你确信你身体健康,没有心脏病、高血压,没有受过重大精神创伤,那我告诉你, 在浮点数的表示范围内,有多于 99.999…% 的数在计算机中是 不能表示 的。 真的是太令人吃惊,也太令人遗憾了。 真相总是很残忍。
请注意我使用的措辞,区别开 不能表示 和 不能精确表示。
下面我从数量级分析一下,32bit 浮点数的表示范围是 10 的 38 次方,而表示个数呢,是 10 的 10 次方。 能够被表示的数只有 1/100000000…. (大概有30个零),这个数多大呢?还记得那个国际象棋和麦子的故事吗?
为了让你了解 指数的威力,我再举个例子:
有一张很大很大的纸,对折 38 次,会有多高呢? 一米?一百米?比珠峰还高?再次考验你心脏承受能力的时刻到了:它不仅仅比珠峰高,其实它已经快到达月球了。
回到原来的话题,还有更残忍的真相。 在剩下的可以表示的不到 0.000…1% 的数中,又有多少不能精确表示呢?这就是我写这篇博客的目的。
上一章中我还给出了一种用定点数精确表示小数的方法。 事实上,手持计算器、java 中的 BigDecimal、C# 中的货币类型、MySQL 中的 NUMERIC 类型就是这么干的。 你还记得在数据库中添加字段时的 SQL 语句是如何写的吗?现在明白为什么我说 再简陋的计算器也比超级计算器的精度高 了吧。
这篇博客我将为大家讲解为什么很多数 不能精确表示,本篇可能比较烧脑子,我会尽量用最通俗的语言,最贴近现实的例子来讲解,不在乎篇幅有多长,关键是要给大家讲明白。下一篇,你将了解到浮点数如何工作,以及为什么很多数 不能表示。
热身 —— 问:要把小数装入计算机,总共分几步?你猜对了,3 步。
•第一步:转换成二进制
•第二步:用二进制科学计算法表示
•第三步:表示成 IEEE 754 形式
在上面的第一步和第三步都有可能 丢失精度。
十进制 VS 二进制
下面我们讨论如何把十进制小数转换成二进制小数(什么?你不会?请自觉去面壁)。
考虑我们将 1/7(七分之一) 写成小数的时候是如何做的?
用 1 除以 7,得到的商就是小数部分,剩下的余数我们继续除以 7,一直除到什么时候结束呢? 有两种情况:
1.
如果余数为 0。yeah!终于结束了,洗洗睡吧
2.
当除到某一步时,余数等于 1… 停!stop!等一下,我发现有什么地方怪怪的。余数为 1,余数如果为 1 的话,再继续除下去,不就又是 1/7 了吗?绕了一个大弯,又回来了?对,你猜的很对,它永远不会结束,它循环了。
注意我上面说的 情况2,我们判断他循环,并 不是从直观看感觉它重复了,而是因为在计算过程中,它又回到了开头。为什么这么说呢?当你计算一个分数时,它总是连续出现 5,出现了好多次,例如 0.5555555… 你也无法断定它是无限循环的,比如 一亿分之五。
记得高中时,从一本数学课外书学到了手动开平方的方法,于是很兴奋的去计算 2 的平方根,发现它的前几位是 1.414,哇,原来「2的平方根」等于 1.414141…。很多天以后,当我再次看到我的笔记时,只能苦笑了,「2的平方根」不可能循环啊,它可是一个无理数啊。
你可能不耐烦了,叽哩哇啦说这么多,有用吗?当然有用了,以后如果 MM 问你:你会爱我到什么时候?你可以回答她:我会爱你到 1/7 的尽头。难道我会把我的表白方式告诉你们吗? 我对你的爱就像圆周率,无限——却永不重复。
扯远了,现在会到主题。 你也许会说:我明白了,循环小数不能精确表示,放到计算机中会丢失精度; 那么有限小数可以精确表示吧,比如 0.1。
对于无限小数,不只是计算机不能精确表示,即使你用别的办法(省略号除外),比如纸、黑板、写字板…都无法精确表示。什么?手机?也不能,当然不能了。不,不,iPad也不行,1万买的也不行,真的,再贵的本子也写不下。
哪些数能精确表示?
那么 0.1 在计算机中可以精确表示吗?
答案是出人意料的, 不能。
在此之前,先思考个问题: 在 0.1 到 0.9 的 9 个小数中,有多少可以用二进制精确表示呢?
我们按照乘以 2 取整数位的方法,把 0.1 表示为二进制(我假设那些不会进制转换的同学已经补习完了):
(1) 0.1 x 2 = 0.2 取整数位 0 得 0.0
(2) 0.2 x 2 = 0.4 取整数位 0 得 0.00
(3) 0.4 x 2 = 0.8 取整数位 0 得 0.000
(4) 0.8 x 2 = 1.6 取整数位 1 得 0.0001
(5) 0.6 x 2 = 0.2 取整数位 1 得 0.00011
(6) 0.2 x 2 = 0.4 取整数位 0 得 0.000110
(7) 0.4 x 2 = 0.8 取整数位 0 得 0.0001100
(8) 0.8 x 2 = 1.6 取整数位 1 得 0.00011001
(9) 0.6 x 2 = 1.2 取整数位 1 得 0.000110011
(n) ...
我们得到一个无限循环的二进制小数 0.000110011…
我为什么要把这个计算过程这么详细的写出来呢?就是为了让你看,多看几遍,再多看几遍,继续看… 还没看出来,好吧,把眼睛揉一下,我提示你,把第一行去掉,从 (2) 开始看,看到 (6),对比一下 (2) 和 (6)。 然后把前两行去掉,从 (3) 开始看…
明白了吧,0.2、0.4、0.6、0.8 都不能精确的表示为二进制小数。 难以置信,这可是所有的偶数啊!那奇数呢? 答案就是:
0.1 到 0.9 的 9 个小数中,只有 0.5 可以用二进制精确的表示。
如果把 0.0 再算上,那么就有两个数可以精确表示,一个奇数 0.5,一个偶数 0.0。 为什么是两个呢?因为计算机二呗,其实计算机还真够二的。
世界上有 10 种人,一种是懂二进制的,一种是不懂二进制的。
其实答案很显然,我再领大家换个角度思考,0.5 就是一半的意思。 在十进制中,进制的基数是 10,而 5 正好是 10 的一半。 2 的一半是多少?当然是 1 了。 所以,十进制的 0.5 就是二进制的 0.1。如果我用八进制呢? 不用计算你就应该立刻回答:0.4;转换成十六进制呢,当然就是 0.8 了。
(0.5)10 = (0.1)2 = (0.4)8 = (0.8)16
如果你还想继续思考,就又会发现一个有趣的事实,我们称之为 定理A。 我们上面的数,都是小数点后面一位小数,因此,在十进制中,这样的小数有 10 个(就是 0 到 9); 同理,在二进制中,如果我们让小数点后面有一位小数,应该有多少个呢?当然是 2 个了(0 和 1)。
哇,好像发现了新大陆一样,很兴奋是吧。那我再给你一棒,其实定理A是错的。再重申一遍 尽信书,则不如无书。我写博客的目的 不是把我的思想灌输到你的脑子里,你应该有自己的思想,自己的思考方式,当我得出这个结论时,你应该立刻反驳我:“按照你的思路,如果是 16 进制的话,应该可以精确表示所有的 0.1 到 0.9 的数甚至还可以精确表示其它的 6 个数。而事实呢,16 进制可以精确表示的数 和 2 进制可以精确表示的数是一样的,只能精确表示 0.5。”
那么到底怎么确定一个数能否精确表示呢?还是回到我们熟悉的十进制分数。
1/2、5/9、34/25 哪些可以写成有限小数?把一个分数化到最简(分子分母无公约数),如果分母的因式分解只有 2 和 5,那么就可以写成有限小数,否则就是无限循环小数。为什么是 2 和 5 呢?因为他们是 10 的因子 10 = 2 x 5。
二进制和十六进制呢?他们的因子只有 2,所以十六进制只是二进制的一种简写形式,它的精度和二进制一样。
如果一个十进制数可以用二进制精确表示,那么它的最后一位肯定是 5。
备注:这是个必要条件,而不是充分条件。一位热心网友设计出了下面的解决精度的方案。我就不解释了,同学们自己思考一下吧。
分享到:
相关推荐
少儿编程-少儿编程系统-少儿编程系统源码-少儿编程管理系统-少儿编程管理系统java代码-少儿编程系统设计与实现-基于ssm的少儿编程系统-基于Web的少儿编程系统设计与实现-少儿编程网站-少儿编程网站代码-少儿编程平台...
编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程软件 mBlock编程...
编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件分享 K2编程器固件...
《C++编程实例100篇》是一本深入浅出的C++编程教程,它以实践为主导,通过丰富的实例帮助学习者掌握C++语言的基础和核心概念。这本书的每个实例都精心设计,旨在帮助初学者和有经验的开发者巩固和提升C++编程技能。 ...
《安捷伦信号源编程指南》是一本专为电子工程师和科研人员编写的实用手册,旨在帮助用户理解和掌握如何利用C语言和C++对安捷伦品牌的信号源进行编程操作。该指南深入浅出地介绍了信号源的基本概念、功能以及在实际...
编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件分享 K2P A2编程器固件...
青少年图形化编程 Scratch编程 儿童编程 消灭新冠病毒 新冠病毒,这个词语在去年年底俨然而生,在今年上半年发展迅速;在我们中国3月2-3日达到了增长的顶峰。也正是因为这个新冠病毒,给我们人民,国家带来了非常...
这份考试检测题模拟测试题附答案是为编程猫考试的准备而设计的,测试题涵盖了编程猫的各种知识点,旨在帮助考生更好地了解编程猫的编程语言和逻辑思维能力。 知识点1: 编程猫的造型切换 在这道题中,编程猫的造型...
OSAI系统CNC编程 OSAI系统CNC编程是计算机numerical控制(CNC)系统的一种编程方法,主要应用于计算机辅助制造(CAM)系统中。OSAI编程语言是一种基于文本的高级编程语言,用于编写CNC机床的控制程序。 OSAI系统...
《Java网络编程第三版》是Java开发者深入理解网络编程的重要参考资料。这本书主要涵盖了Java平台上的网络应用程序开发,从基础概念到高级技术,为读者提供了一套全面的学习路径。以下是本书中涉及的一些关键知识点:...
扫描版 超清百度网盘资源!!! 为初学者准备的网络编程! 韩国TCP/IP经典教程!... 本书针对网络编程初学者,面向具备C语言基础的套接字网络编程学习者,适合所有希望学习Linux和Windows网络编程的人。
欧姆龙ST编程中文手册 本手册为欧姆龙ST语言编程教程实例,旨在帮助用户学习和掌握欧姆龙ST语言编程的基础知识和技术。手册中包含了丰富的内容,包括功能块介绍、编程指导、注意事项等。 第一章:介绍 在本章中,...
根据给定文件的信息,我们可以提炼出关于C++游戏编程的一些关键知识点。首先,标题“C++游戏编程入门.pdf”明确指出这是一本介绍如何使用C++进行游戏开发的教材。C++是一种广泛应用于游戏开发的强大编程语言,因为它...
《WINDOWS高级编程指南》是Jeffrey Richter所著,深入探讨了32位Windows平台的高级编程技术。该书是编程领域的经典之作,特别适合于熟悉Windows编程但希望更深入理解其高级概念的读者,如内存管理、消息处理、动态...
多处理器编程要求理解新型计算原理、算法及编程工具,至今很少有人能够精通这门编程艺术。 现今,大多数工程技术人员都是通过艰辛的反复实践、求助有经验的朋友来学习多处理器编程技巧。这本最新的权威著作致力于...
《Visual C++网络编程案例实战》一书深入探讨了如何利用Visual C++及MFC类库进行网络编程,尤其强调了Windows Socket的应用。本书不仅覆盖了理论基础,还提供了丰富的实践案例,帮助读者掌握网络编程的核心技能。 #...
【少儿编程】是一种针对儿童和青少年的编程教育,旨在通过早期接触编程,培养他们的逻辑思维、创新能力和问题解决能力。近年来,随着科技的发展和人工智能的崛起,编程技能被视为未来社会的关键素养之一,因此少儿...
【标题】"TOP851编程器软件"指的是专门用于编程和烧录TOP851型号编程器的软件工具。这种编程器常被用于电子工程领域,尤其是对微控制器(MCU)进行编程,比如8位或16位的单片机。在描述中提到,该软件适用于较旧的...
Scratch 3.0是麻省理工学院(MIT)媒体实验室终身幼儿园团队开发的一款面向儿童的图形化编程工具,旨在让孩子们通过拖拽积木式的编程块来学习编程概念。"大龙老师新版scratch3.0编程全套素材"提供了一整套的教学资源,...