10081677wc / blog

78 stars 6 forks source link

理解前端数据类型 #5

Open 10081677wc opened 6 years ago

10081677wc commented 6 years ago

数据类型

基本数据类型:String,Boolean,Number,undefined,null,Symbol(ES6) 引用数据类型:Object,Functoin,Array,RegExp 等

堆(heap)和栈(stack)

堆和栈都是存放临时数据的内存空间(注意与数据结构堆、栈的区分):

栈时向低地址扩展的数据结构,是一块连续的内存的区域(最大容量是系统预设好的),由编译器自动分配和释放,速度快,用于存放函数的参数值与局部变量的值,只要栈的剩余空间大于申请空间就分配,否则报错异常,其操作方式类似于数据结构中的栈,是先进后出的。

基本数据类型的值保存在栈内存中的简单数据段,按值访问。

堆是在程序运行时(而不是在程序编译时)申请的内存空间,即动态分配内存对其访问(大小受到虚拟内存影响),一般由程序员分配和释放,若没有手动释放则在程序结束时由 OS 回收,操作方式与数据结构中的堆是两回事,类似于链表。

引用数据类型的值是指保存在堆内存中的对象,由于对象的大小不固定不能保存在栈内存中,然而内存地址的大小是固定的,故可以将其保存在栈内存中,也就是说:变量在栈内存中保存的数据实际上是指向堆内存中保存的对象的指针。

对于堆,操作系统有一个记录空闲内存地址的链表,当系统收到申请时,会遍历该链表寻找第一个空间大于所申请空间的堆节点,然后将该节点从空闲区链表中删除,将该节点的空间分配给程序,若是找到的节点地址空间大于申请的大小,系统会把剩余的节点空间重新添加到内存空闲区链表中。 (故而使用堆时内存地址不连续,且容易产生碎片)

内存中的其他空间:

全局区(静态区):存储全局变量和静态变量,程序结束后由系统释放。

文字常量区:存储常量字符串

char *p1; // p1 保存在栈中
char *p2 = "test"; // p2 保存在栈中,test 保存在常量区

程序代码区:存储程序的二进制代码

关于 Number 数字类型

In JavaScript, Number is a numeric data type in the double-precision 64-bit floating point format (IEEE 754). In other programming languages different numeric types can exist, for examples: Integers, Floats, Doubles, or Bignums.

根据 MDN 关于 Number 的描述 可知,javascript 的数字类型只有 number 一种,使用 IEEE754 标准中的双精度浮点数来存储,长度为64位。

符号位 指数位 小数位
0 00000000000 00000...0000000000000000000000000
1 bit 11 bit 52 bit

问题:根据 IEEE754 计算 0.1 + 0.2 = ?

使用乘二取整法计算 0.1 的二进制表示形式

0.1 = (0.00011)2 0011 无限循环 = (-1)^0 2^(-4) (1.1001)2 1001 无限循环

0.2 = (-1)^0 2^(-3) (1.1001)2 1001 无限循环

由于小数位仅储存 52 bit, 储存时会将超出精度部分进行 "零舍一入"

值类型 小数位存储范围内 存储范围外
无限精确值 1001 1001 1001 1001 ... 1001 1001 1001 1001 1001...
实际存储值 1001 1001 1001 1001 ... 1001 1001 1010 -

故而 0.1 和 0.2 的浮点数存储形式表示为:

浮点数值 符号位 指数值 小数位
0.1 0 -4 1001 1001 1001 1001 ... 1001 1001 1010
0.2 0 -3 1001 1001 1001 1001 ... 1001 1001 1010

0.1 + 0.2

在计算浮点数相加时需要先进行“对位”,将较小的指数化为较大的指数,并将小数部分相应右移。

0.1 = (−1)^0 2^(−3) (0.11001100110011001100110011001100110011001100110011010)2

0.2 = (−1)^0 2^(−3) (1.1001100110011001100110011001100110011001100110011010)2

0.1 + 0.2 = (−1)^0 2^(−2) (1.0011001100110011001100110011001100110011001100110100)2 = 0.30000000000000004

解决方案

排除直接使用超大数或者超小数的情况,出现这种问题基本是浮点数的小数部分在转成二进制时丢失精度造成的,所以我们可以将小数部分转换成整数后再计算,需要注意的是乘法操作也是一种浮点数计算,在转换过程中可能存在精度问题。

所以不要直接通过计算将小数转换成整数!我们可以通过字符串操作:移动小数点的位置来转换成整数,最后再同样通过字符串操作转换回小数。