机器眼睛里的小数

前言

这篇文章我想写下关于最近我学习的小数部分的内容。
本文首次发表于https://segmentfault.com/a/11...

A:what? 小数,什么鬼?
B:我研究过大数相加,你说的是那个大数对应的小数吗?

你没听错,就是小数,不过是像 0.1,0.75,1.5这样的小数,不是很小的整数哟( ⊙ o ⊙ )!

关于小数的问题

关于小数你了解多少呢?先来一个小数的运算语句。

Number(0.1).toString(2)
// 如果你把这个语句JavaScript引擎里执行下,你会得到下面这个字符串
// "0.0001100110011001100110011001100110011001100110011001101"

如果这个难不到你,那三步走,拿起你的小黑鼠,移到页面上部的X,按下左键,然后88。因为本文不适合你了?(有脾气了)
如果不幸,你被难倒了,那我?(斜眼笑)。

解释奇怪的字符串

要解释这个字符串的来历,可得费点功夫了,施主,你准备好瓜子,板凳了吗?
(长篇警告)
鉴于你们都不耐其烦的看到这了,那我就耐心的来扒一扒,为什么是这么个字符串。。。
我们分为下面几步来解释下这个字符串的来历:

  1. 上面问题中JavaScript语句的功能
  2. 信息在计算机中是如何存储的
  3. 小数在计算机中是如何存储的
  4. 小数在JavaScript中是如何表示的
  5. 这条语句结果的分析

那么我们就逐步来:

语句功能

我们的语句是 Number(0.1).toString(2)
我们拆分下这个复合语句

let wrapNumber = Number(0.1);                 (1)
let binaryNumber = wrapNumber.toString(2);    (2)

语句(1)将一个基本类型的数值0.1,使用数值类型的构造器将其包装成wrapNumber
补充内容:关于wrapNumber的类型
因为原始数值类型的数值存在一些有用的方法,比如 toFixed方法,
和我们第二行语句中的toString()方法等。
[toString([radix])][3]方法的将数值转换成一个radix进制数值对象的字符串
所以到这一步,我们应该知道,"0.0001100110011001100110011001100110011001100110011001101" 代表的是十进制数0.1的2进制表示形式
如果你看了这篇就懂了,那就不用继续看下去了
要了解这个字符串我们必须得了解一下,数据在计算机里是怎么表示的,又是怎么样运算的?

数据的表示

计算机中的信息表示

计算机内部的实现:在现代计算机内部,所有的信息(图,声,字符串,数值等)都是通过二进制数值的形式来表示的;
推断:只要理解了二进制数表示信息的方法及其运算原理,对于理解小数在计算机中的存储那就更进一步了。

计算机中使用二进制表示数据

那么现实中的计算机为什么内部使用二进制来表示信息呢,我们人不是更习惯十进制嘛,为什么不让机器也弄十进制呀。
这就得从计算机的组成说起,计算机都是由数字集成电路电子部件组成的。而这些电子部件都会有和其他部件连接的引脚,而所有的引脚上只有直流电压0V和5V俩个状态。二进制数刚好只有俩个状态,与电子部件的能表示俩个状态的特性刚好吻合,决定了计算机中的信息用二进制数来表示最为简单和高效。所以计算机处理信息的最小单位--位,就相当于二进制中的一位。位的英文bit是二进制数位(binary digit)的缩写。
而计算机中处理信息的基本计量单位是字节(Byte). 8位为1个字节. 位是最小单位,字节是基本单位。
所以我们表示
十进制的1, 二进制:00000001 一个字节来表示
十进制的6, 二进制:00100111 00000110(Golden纠正) 一个字节来表示 0~2^8-1的数共2^8个数
十进制的512,二进制: 000000001 000000000 俩个字节来表示 0~2^16-1共2^16个数
二进制数转十进制数的方法,简单概括为:按权相加。
clipboard.png

对于XX进制数之间的转换我在这里就不赘述了,不是本篇文章讨论的核心问题:
所以程序中使用的十进制数,在计算机中会被编译为二进制数进行运算。
更多详细的内容:请参考《程序是怎么跑起来的》:中第二章-数据是用二进制数表示的

小数的表示法

小数和浮点数的区别相关文章可以参考迷渡大佬的文章
代码之谜(四)- 浮点数(从惊讶到思考)

那么我们再想想,十进制整数都是可以成功使用更多位的二进制数来表示,那小数呢?
比如从0~1就有无限多个小数,而计算机中用什么样的方式来表示这无限多个小数呢?
答案是不能全部表示; 惊讶后又点了点头.jpg

根据现在编程语言广泛采用的国际标准IEEE 754中制定的浮点数的表示方式
下面三张图来自《程序是怎么跑起来的》
clipboard.png
双精度和单精度对应的指数和尾数的长度入下图:
clipboard.png
依据浮点数表示法,我们可以将0.75表示成
clipboard.png

为了表示数的唯一性,标准对表示的形式也做了细致的规定:只能使用图3-5 中第一行的这种形式

如果看完上图还未理解本小节内容,请参考下面推荐的文章;
浮点数的二进制表示
注:看这篇文章可以更细致的理解这节的内容
《程序是怎么跑起来的第三章》:计算机进行小数计算时出错的原因
注:看这篇可以理解到更多计算机的机制相关内容
我的英文不够好,其他同学如果英文好:请直接阅读IEEE 754,WIKI中的内容
IEEE 754 双精度 64 位浮点数

JavaScript中的小数

我们继续探索一下JavaScript中小数的表示:Annotated ECMAScript 5.1
ES6中Number类型的描述:

primitive value corresponding to a double-precision 64-bit binary format IEEE 754-2008 value
Number类型的原始值对应于双精度64位二进制格式IEEE 754-2008的值;
所以说ECMA中的数都是采用IEEE754标准,小数的表示和我们看的上面的浮点数的表示是一致的。
注意: 整数也是这么表示的哦,只不过指数部分为正数而已。
详解 JavaScript 数据类型
分析到这里,应该知道这个字符串是怎么来的了吧.

这个字符串的生成

可以利用下面这个小公式生成十进制小数对应的二进制字符串:
一个js的小工具:
科学计数法表示的十进制浮点数转二进制字符串
如果你想写一个可以参考这篇文章中的步骤或者:
ToString Applied to the Number Type
基础野:细说浮点数
这篇文章主要是想去让自己了解浮点数的内容;
不过看了许多文章,都会提到规避这种计算机无法精确表示浮点数的实用方法,方便以后查阅。
方法一、小数转成整数后计算;
方法二、利用bignumber

补充部分:关于wrapNumber的类型

有人可能就有疑问了,这个wrapNumber是什么类型,是Number类型的实例对象吗?
我们用下面语句测试下

typeof wrapNumber === 'number'
Object.prototype.toString.call(wrapNumber) === "[object Number]"
Object.prototype.toString.call(0.1) === "[object Number]" //补充部分

结果一致,均可检测出wrapNumber为原始数值类型
但是它却不是Number类型的实例对象,仅仅是一个Number类型的值, 从下面语句可以看出。

wrapNumber instanceof Number === false
wrapNumber instanceof Object === false
wrapNumber === 0.1 // 值
let wrapNumberObj = new Number(0.1);
typeof wrapNumberObj === 'object'
wrapNumberObj instanceof Number === true
wrapNumberObj instanceof Object === true
Object.prototype.toString.call(wrapNumberObj) === "[object Number]"
wrapNumberObj.valueOf() === 0.1 //new 对象的取值方式

MDN中Number类型中关于无new构造的描述如下:

In a non-constructor context (i.e., without the new operator), Number can be used to perform a type conversion.

所以大家在做类型检查的时候还是要区分下new构造和无new构造的区别;

相似文章:
JS魔法堂:彻底理解0.1 + 0.2 === 0.30000000000000004的背后
该死的IEEE-754浮点数,说「约」就「约」,你的底线呢?以JS的名义来好好查查你
浮点数那些事儿
http://justjavac.com/codepuzz...
http://justjavac.com/codepuzz...
参考文章:
IEEE 754-2008
sec-terms-and-definitions-number-value
JavaScript 中小数和大整数的精度丢失

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算机组成原理算法实现代码及报告 1)系统进入(主)窗体的设计:菜单需要在输入口令正确后方可激活使用。口令输入错误时要给出重新输入口令的提示,三次口令输入错误应该禁止使用。 有四个菜单,分别是“逻辑运算”、“进行定点整数单符号位补码加减法”、“定点整数原码乘法”和“浮点数的加减运算”口令输入正确后菜单激活,按相应菜单进入相应窗口。 (2)选择主窗体中“逻辑运算”时进入逻辑运算窗体: ①两个输入框各输入一个数; ②一个结果输出框输出结果; ③八个按钮如下: 按“输入”将输入焦点设置为最上面的一个文本框上。依次输入两个(或一个)二进制数(如110101或110101) 按“逻辑非”、“逻辑加”、“逻辑乘”或“逻辑异”按扭中的任一个后,将在第三个文本框中显示对应操作的结果。 选择“返回”按扭时回到主窗体 (3)选择主窗体中“进行定点整数单符号位补码加减法”时进入进行定点整数单符号位补码加减法窗体: ①两个输入框各输入一个数; ②两个结果输出框分别输出加法结果和减法结果; ③四个按钮 按“输入”将输入焦点设置为最上面的一个文本框上依次输入两个(或一个)二进制数(如110101或110101)其中第一位是符号位0为正1为负 按“加法”后在加法结果输出框中显示对应操作的结果 按“减法”后在减法结果输出框中显示对应操作的结果 按“返回”按扭时回到主窗体 (4)选择主窗体中“定点整数原码乘法”时进入进行定点整数原码乘法窗体: ①两个输入框各输入一个数; ②一个结果输出框输出结果; ③三个按钮 按“输入”将输入焦点设置为最上面的一个文本框上依次输入两个(或一个)二进制数(如110101或110101) 按“乘法”后在结果输入框中显示对应操作的结果 按“返回”按扭时回到主窗体 (5)选择主窗体中“浮点数的加减运算”时进入浮点数的加减运算窗体: ①四个输入框分别输入第一个数的阶码和尾数及第二个数的阶码和尾数; ②四个个结果输出框分别输出加法结果的阶码和尾数及减法结果的阶码和尾数; ③四个按钮 按“输入”将输入焦点设置为最上面的一个文本框上依次输入二进制数 按“加法”后在加法结果输出框中显示对应操作的结果 按“减法”后在减法结果输出框中显示对应操作的结果 按“返回”按扭时回到主窗体
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值