跳到主要内容

认识

2023年02月10日
柏拉文
越努力,越幸运

Number 值表示像 37-9.25 这样的浮点数值。

一、存储结构


JavaScript的第一个版本中,单个值在栈中占据32位的存储单元。存储单元分为两个部分:一部分是标记位,另一部分是数据。标记位为001表示Integer类型,标记位为010表示double类型

二、数值范围


安全整数: JavaScript 能表示并且能够精确算数运算的安全整数范围为[-2^53 -1, 2^53 - 1][-9007199254740991,9007199254740991] ,获取安全整数的方法如下:

  • Math.pow()计算得出:

    Math.pow(2, 53) - 1 // 9007199254740991
    -Math.pow(2, 53) - 1 // -9007199254740991
  • Number.MAX、Number.MIN 获取:

    console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
    console.log(Number.MIN_SAFE_INTEGER) // -9007199254740991

超出安全的整数: 超出安全的整数,不能表示或者不能精确的算数运算,需要使用BigInt 来计算,如下所示

console.log(9007199254740991 + 2) // 9007199254740992

console.log(BigInt(9007199254740991) + BigInt(2)) // 9007199254740993n

三、精度丢失


问题

为什么 0.1 + 0.2 的计算结果,不等于 0.3

原因

  1. 对于Number类型在计算机内存中通过64(1+11+52)位来表示一个数字

    • 第0位: 1 位。符号位,0表示正数,1表示负数(s)

    • 第1位到第11位: 11 位。储存指数部分(e)

    • 第12位到第63位: 52 位。储存小数部分(即有效数字)f 。对于尾数部分(小数部分)而言,在规约形式下第一位默认为1,通常省略不写。所以,JavaScript提供的有效数字最长为53个二进制位(64位浮点的后52位+被省略的1位),这也是 JS 最大安全数是 Number.MAX_SAFE_INTEGER == Math.pow(2,53) - 1, 而不是Math.pow(2,52) - 1 的原因。

  2. 对于0.1 + 0.2 , 计算机处理的过程为: 如果要计算 0.1 + 0.2 那么计算机会分别把 0.10.2 转成二进制,然后进行对阶运算

    1. 二进制转换: 0.10.2 转化为二进制的过程: 浮点数转化为二进制会发生无限循环,由于有尾数位数的限制,需要将后面多余的位截掉, 这样在进制之间的转换中精度已经损失

      • 问题:

        • 为什么 x=0.1 能得到 0.1 ?

          答: 标准中规定尾数f的固定长度是52位,再加上省略的一位,这53位是JS精度范围。它最大可以表示2^53(9007199254740992), 长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理, 这个就是为什么0.1可以等于0.1的原因

    2. 对阶运算: 由于指数位数不相同,运算时需要对阶运算 这部分也可能产生精度损失。

解决方案

精度丢失解决办法就是先将小数转换成整数,然后用整数进行计算得到结果后再转换为小数

解决方案实现

// 判断number是否为一个整数
const isInteger = function (number) {
return Math.floor(number) === number
}

// 四舍五入
const toFixed = function (number, decimalLength = 0) {
var times = Math.pow(10, decimalLength)
var fixed = number * times + 0.5
return parseInt(fixed) / times
}

// 将一个浮点数转成整数,返回整数和倍数
const toInteger = function (floatNumber) {
const numberInfo = { times: 1, number: 0 }
const isNegative = floatNumber < 0
if (isInteger(floatNumber)) {
numberInfo.number = floatNumber
return numberInfo
}
const stringFloatNumber = String(floatNumber)
const dotPosition = stringFloatNumber.indexOf('.')
const length = stringFloatNumber.substr(dotPosition + 1).length
numberInfo.times = Math.pow(10, length)
numberInfo.number = toFixed(Math.abs(floatNumber) * numberInfo.times)
if (isNegative) numberInfo.number = -numberInfo.number
return numberInfo
}

// 加
export const add = function (number1, number2, decimalLength = 0) {
const { number: num1, times: times1 } = toInteger(number1)
const { number: num2, times: times2 } = toInteger(number2)
const maxTimes = Math.max(times1, times2)
let result
if (times1 === times2) result = (num1 + num2) / maxTimes
if (times1 > times2) result = (num1 + num2 * (times1 / times2)) / maxTimes
if (times1 < times2) result = (num1 * (times2 / times1) + num2) / maxTimes
return toFixed(result, decimalLength)
}

参考资料


JavaScript 的 8 种数据类型以及它们的底层数据结构

必考知识点-JavaScript类型转换(讲原理)