JS 实现将浮点数转换成二进制( 32 位)

1,070 阅读3分钟

讲解部分今天先不消化了,感谢陈浩老师的讲解,下面是陈浩老师的原话:

下面,让我来试着解释一下浮点数的那三段表示什么意思。

  • 第一段符号位。对于这一段,我相信应该没有人不能理解。
  • 第二段指数位。什么叫指数?也就是说,对于任何数 x,其都可以找到一个 n,使得 2n<=x<=2n+1。比如:对于 3 来说,因为 2 < 3 < 4,所以 n=1。而浮点数的这个指数为了要表示 0.00x 的小数,所以需要有负数,这 8 个 bits 本来可以表示 0-255。为了表示负的,取值要放在 [-127,128] 这个区间中。这就是为什么我们在上面的公式中看到的 2(E−127) 这一项了。也就是说,n=E−127,如果 n=1,那么 E 就是 128 了。
  • 第三段尾数位。也就是小数位,但是这里叫偏移量可能好一些。这里的取值是从[ 0 - 223]中。你可以认为,我们把一条线分成 223 个线段,也就是 8388608 个线段。也就是说,把 2n 到 2n+1 分成了 8388608 个线段。而存储的 M 值,就是从 2n 到 x 要经过多少个段。这要计算一下,2n 到 x 的长度占 2n 到 2n+1 长度的比例是多少。

今天主要是使用 JS 实现。

下面是准备的函数,第一个是将正整数转换成二进制的字符串;第二个是按要求补零操作。

/**
 * 求一个正数的二进制
 * @param {number} d 正数
 * @returns {string} 二进制
 */
function uitoa(d) {
  let strN = "";
  const _uitoa = (d) => {
    if (d === 0) {
      return;
    }
    strN = (d % 2) + strN;
    _uitoa(Math.floor(d / 2));
  };
  _uitoa(d);
  return strN;
}
/**
 * 按给定的 n 将传入的二进制补上 0
 * @param {string} bStr 要补零的二进制
 * @param {number} n 要求的位数
 * @returns {string} 经过补零操作以后的二进制
 */
function zeroFill(bStr, n) {
  let bArr = bStr.split("");
  let needZero = n - bArr.length;
  for (let i = 0; i < needZero; i++) {
    bArr.unshift("0");
  }
  return bArr.join("");
}

下面就是具体实现了:

function bPrint(f) {
  let signBit; // 保存符号的字符串
  let exponent; // 保存指数的字符串
  let decimal; // 保存小数部分的字符串
  // 首先求出负号位,就是看是正数还是负数
  signBit = f > 0 ? "0" : "1";
  // 求出指数部分
  let i = 0; // 用于求出接近的指数
  let fl = Math.abs(f); // 先前已经处理符号位了,所以我们直接操作正数
  let increase = fl < 1 ? -1 : 1; // 看是不是小于 1 的数,是的话在求近似值的时候不一样
  while (true) {
    const pow2 = Math.pow(2, i);
    if (increase === 1 && fl < pow2) {
      // 如果是大于 1 的数,求小于
      break;
    } else if (increase === -1 && fl > pow2) {
      //如果是小于 1 的数,求大于
      break;
    }
    i += increase;
  }
  const k = i - (increase === 1 ? 1 : 0); // 保存的是要操作的数,这样下面就不用都判断了
  exponent = zeroFill(uitoa(127 + k), 8);
  // 小数部分
  let radio = (f - Math.pow(2, k)) / (Math.pow(2, k + 1) - Math.pow(2, k));
  decimal = zeroFill(uitoa(Math.round(radio * Math.pow(2, 23))), 23);
  return signBit + exponent + decimal;
}

思路很简单,就是一部分一部分的求,规则上面都有,所以很简单,当然这也是我是码农的原因。同时也说明一件事情,只要把数学模型建好了一切都很简单。

看不懂代码的可以先看上面的原理,我就不过多解释了,不然就画蛇添足了。

我觉得我写代码永远都是按常规思路写,没有什么抽象可言。但我觉得我写的代码应该很容易懂吧,看到的人,如果认真看了,麻烦评价一下。

我想说明一下,测试验证是不是正确,可以使用 xcode ,只要加上断点在下面的 debug 窗口都能看到,默认显示的值是十进制,修改一下显示方式即可。记得创建 C 语言的程序,其他的我没看,但 C 语言的验证可以。