前端练习39 Math.clz32的Polyfill

知识点

  1. Math.clz32方法
  2. 求负数的二进制编码的方法
  3. 求小数的二进制编码的方法
  4. 填充字符串的方法padStart

题目

ES6新增了Math.clz32方法,可以让我们获取到一个整数的无符号32位的二进制形式有多少位前置的 0。例如:

Math.clz32('2') // => 30

如果不能转换成数字的,返回32

Math.clz32('good') // => 32

完成一个clz32函数,要求返回结果和Math.clz32方法保持一致

实现

一开始做的时候,又犯傻比了,Math.clz32方法返回的是开头的0的个数,对于0,返回值是32,对于负数,由于标志位是1,所以返回值一定是0

我花了一些力气去求出负数的二进制编码:

// 求负数的二进制
const getNegativeBinary = num => {
  // 求相反数的二进制编码
  const positiveBinary = (-num).toString(2).padStart(32, '0');
  // 求反码
  const reverseBinary = positiveBinary.split('').map(v => +v === 1 ? 0 : 1).join('');
  // 求反码的十进制数
  const reverseDecimal = parseInt(reverseBinary, 2);
  // 求补码
  const complement = reverseDecimal + 1;
  // 求补码的二进制
  const str = complement.toString(2);
  return handleString(str);
};

实际上没有必要,如果真的要求出的话,只需要使用按位操作符中的无符号右移(>>>)操作即可:

// 求负数的二进制
const getNegativeBinary = num => (num >>> 0).toString(2);

另外,对于小数,Math.clz32方法返回值也是0,所以只需要对整数部分进行处理即可

直接取整的方法在《JS49 判断整数类型和直接取整的方法》进行了总结,使用parseInt的话,对于科学计数法表示的小数(例如1e07)取整结果是错误的,而这里操作的是32位的无符号整数,所以可以使用按位操作符来取整:

~~num;
num >>> 0
num | 0
num ^ 0
num & -1

要注意的是,如果对小数使用toString(2)方法,返回值是由整数的二进制表达式加上小数点再加上小数的二进制表示(小数的二进制表示是通过“乘二取整, 顺序排列”实现的):

(1.1).toString(2)
// "1.000110011001100110011001100110011001100110011001101"

按照我一开始思路实现,要考虑的边界条件很多,但是其实很多都是没有必要的,简化了一下之后是这样:

// 处理有可能超过32位的情况
const handleString = str => {
  if (str.length <= 32) {
    return str.padStart(32, '0')
  }
  return str.slice(str.length - 32)
};

// 求负数的二进制
const getNegativeBinary = num => {
  // 求相反数的二进制编码
  const positiveBinary = (-num).toString(2).padStart(32, '0');
  // 求反码
  const reverseBinary = positiveBinary.split('').map(v => +v === 1 ? 0 : 1).join('');
  // 求反码的十进制数
  const reverseDecimal = parseInt(reverseBinary, 2);
  // 求补码
  const complement = reverseDecimal + 1;
  // 求补码的二进制
  const str = complement.toString(2);
  return handleString(str);
};

// 找到前置的0的个数
const countZero = number => {
  if (number > 0) {
    const str = number.toString(2);
    return handleString(str).indexOf('1');
  } else {
    return getNegativeBinary(number).indexOf('1');
  }
};

const clz32 = (num) => {
  if (num === Infinity || num === -Infinity || num === 0 || !+num) {
    return 32
  }
  let count = -1;
  // 整数
  count = countZero(~~num);
  // 如果没找到1,返回32
  return count < 0 ? 32 : count

实际上,根本没有必要如此复杂:

const clz32 = (num) => {
  const number = num >>> 0;
  return number ? 32 - number.toString(2).length : 32
};

关键的就是const number = num >>> 0;,对小数、负数、非数值类型都处理了(非数值类型的结果是0)

参考

点赞