数字转IEEE754双精度二进制
为了更好了解浮点数不准确的原因,所以就特定学习了一下双精度,并写了一个函数,可以将浮点数转为双精度二进制。
一个浮点数可以分为整数
和小数
部分,将整数与小数转为二进制字符串时,其运算步骤是不一样的。
转整数部分
整数转换的大致步骤如图所示:
代码如下:
/**
* 整数部分转为二进制字符串
* @param {bigint} bigInt 整数部分
* @returns 二进制字符串
*/
function integerToBinary(bigInt) {
// 将整数转为二进制
let binarys = "";
while (bigInt > 0n) {
binarys = (bigInt % 2n) + binarys;
bigInt /= 2n;
}
return binarys || '0';
}
小数部分
小数转二进制比较的复杂,如图所示:
在绝大多数情况下,一直乘2后的值%10都不为0,所以会一直取值下去,这也是为啥小数不能精确保存的原因。
代码如下:
/**
* 小数部分转二进制
* @param {bigint} bigDot 小数部分
* @param {bigint} dotLength 小数部分长度
*/
function dotToBinary(bigDot, dotLength) {
// 假如bigDot为36n,dotLength为2
// 二进制小数
let dots = "";
// 基数为100n
const baseN = 10n ** dotLength;
// 最多生成64位小数
while (dots.length < 64) {
// 下面是小数转二进制的计算方法
// 获取小数部分*2的结果
bigDot = bigDot * 2n;
// 如果能被10整除,则到此为止,二进制小数+1
if (bigDot % 10n === 0n) {
dots += 1;
break;
}
// 如果乘2后进位了,则二进制小数+1,并移除最高位
else if (bigDot / baseN >= 1n) {
bigDot = bigDot % baseN;
dots += 1;
}
// 乘2后未进位,则二进制小数+0
else {
dots += 0;
}
}
return dots;
}
IEEE754的组成
该标准在存储数字时,由三部分组成,如图所示
在精度为64时,表示一共有64位,其中首位为符号位,接下来的11位为值为指数位记作e,再接下来的52位为分数位m
最终值为(+/-)1.m * (2 ** e)
在计算e
时,以2 ** 10 - 1
值为基准,再加上或减去.
需要移动的位数
以12.5
举例,此值的toString(2)
值为1100.1
,将.
往左移动3
位(e=1023+3
)后得到1.1001
,m
值为1001
+48个0
以0.25
举例,此值的toString(2)
值为0.01
,此时e = 1023 - 2
,m值为52个0
代码如下(仅供参考):
function toBinaryStr(num) {
// 将数字拆分为整数(12)和小数(8)部分
const [integerStr, dotStr] = String(num).split(".");
const integer = Math.abs(Number(integerStr));
const dot = Math.abs(Number(dotStr));
// 记录第一个符号位
const markPos = Object.is(num, 0) || num > 0 ? "0" : "1";
// 将整数转为二进制
const integers = integerToBinary(BigInt(integer));
// 小数部分转为二进制
let dots = dot ? dotToBinary(BigInt(dot), BigInt(dotStr.length)) : "";
let dbStr = integers + "." + dots;
// 计算.移动的偏差值
let diffBit;
if (integers !== "0") {
diffBit = BigInt(integers.length - 1);
} else {
const indexOne = dbStr.indexOf("1");
diffBit = BigInt(dbStr.indexOf(".") - indexOne);
dbStr = dbStr.slice(indexOne);
}
// 指数位,一共有11位,如果不足在前面用0补足
// 1023是一个基准值,此值表示指数为0,即整数部分为个位数且不是0
const exponentBit = integerToBinary(2n ** 10n - 1n + diffBit);
let binarys =
markPos + " " + exponentBit.padStart(11, "0") + " " + dbStr.slice(1);
return binarys.slice(0, 66).padEnd(66, 0);
}