数字转IEEE754双精度二进制

27 阅读3分钟

数字转IEEE754双精度二进制

为了更好了解浮点数不准确的原因,所以就特定学习了一下双精度,并写了一个函数,可以将浮点数转为双精度二进制。

一个浮点数可以分为整数小数部分,将整数与小数转为二进制字符串时,其运算步骤是不一样的。

转整数部分

整数转换的大致步骤如图所示:

1705149317446(1)(1).png

代码如下:

/**
 * 整数部分转为二进制字符串
 * @param {bigint} bigInt 整数部分
 * @returns 二进制字符串
 */
function integerToBinary(bigInt) {
    // 将整数转为二进制
    let binarys = "";
    while (bigInt > 0n) {
        binarys = (bigInt % 2n) + binarys;
        bigInt /= 2n;
    }
    return binarys || '0';
}

小数部分

小数转二进制比较的复杂,如图所示:

Screenshot_2024-01-13-20-45-57-532_com.miui.notes.jpg

在绝大多数情况下,一直乘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的组成

该标准在存储数字时,由三部分组成,如图所示

Screenshot_2024-01-13-20-49-11-143_com.miui.notes.jpg

在精度为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.1001m值为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);
}