一 目录
不折腾的前端,和咸鱼有什么区别
| 目录 |
|---|
| 一 目录 |
| 二 前言 |
| 三 字符串求和/求差 |
| 四 十进制转二进制 |
| 五 二进制转十进制 |
| 六 总结 |
二 前言
在刷题的生涯中,你会碰到让你尴尬的一个问题:
- 十进制转二进制怎么转?
- 二进制转十进制怎么转?
如果小伙伴对 JS 的 API 相当熟练,那么就是信手拈来:
- 十进制转二进制:
num.toString(2) - 二进制转十进制:
parseInt(num, 2)
因为这两个问题在 JavaScript 中都提供了工具,所以直接对需要转换的数套用这两个即可。
然而,咱们更进一步探讨下:
- 如果需要提升性能
- 如果需要转换大数
为什么要这么说?咱们拿 toString() 来看:
阐述:toString() 方法返回指定 Number 对象的字符串表示形式。
语法:numObj.toString([radix])
参数:radix
指定要用于数字到字符串的转换的基数(从2到36)。
如果未指定 radix 参数,则默认值为 10。
描述:
如果基数没有指定,则使用 10。
如果对象是负数,则会保留负号。
即使 radix 是 2 时也是如此:
返回的字符串包含一个负号(-)前缀和正数的二进制表示,
不是 数值的二进制补码。
很好,看到这里我们可以清晰清楚,Number.prototype.toString() 对于负数是无能为力的。
同时,在 LeetCode 题目【1018-可被5整除的二进制前缀】中,jsliang 还发现,大数(超过 JavaScript 的 Number.MAX_SAFE_INTEGER 限制)也无法被准确转换成二进制。
这种情况下,迫切希望知道如何将二进制转出十进制的个位数来,因为个位数为 5 或者为 0 才能被 5 整除。
但是,找了老久,网上搜索 JS 二进制转十进制,得到的全是 num.toString(2) 或者 parseInt(num, 2)。
无法,换了关键字,直接搜 二进制转十进制,找到几篇不错的非代码讲解:
那么咱们开始转换成编程语言,这样以后刷题,咱们就可以直接套用了!
三 字符串求和/求差
jsliang 在编写这篇文章的过程中,发现字符串求和的可以弄成共用函数,然后直接打爆里面 2 道题:
字符串求和(支持 2 进制和 10 进制)
/**
* @name stringAddtion
* @description 字符串求和
* @param {string} a 加数一
* @param {string} b 加数二
* @param {any} base 基数(默认十进制)
* @return 返回计算结果
*/
const stringAddtion = (a = '', b = '', base = 10) => {
// 结果
let result = '';
// 进位标记
let carry = 0;
// 设置 a、b 的长度,方便逆序遍历
let aIndex = a.length - 1, bIndex = b.length - 1;
while (aIndex >= 0 || bIndex >= 0) { // a 或 b 还有位可以相加
// aIndex bIndex可能为负数值,需要转化为 0
sum = (+a[aIndex] || 0) + (+b[bIndex] || 0) + carry;
// 是否需要进位
carry = sum >= base ? 1 : 0;
// 计算最终结果
result = sum % base + result;
// 移位后往更高位靠
aIndex--;
bIndex--;
}
// 如果计算完毕后还有进位,那么前面 + 1
if (carry) {
result = '1' + result;
}
// 返回最终结果
return result;
};
同样,字符串求差有没有题目不知道,因为 jsliang 刷题太多,忘记是否有求差的了,但是因为下面会用到,所以这里提取出来放着:
字符串求差(支持 2 进制和 10 进制)
/**
* @name stringDifference
* @description 字符串求差
* @param {string} a 减数一
* @param {string} b 减数二
* @param {any} base 基数(默认十进制)
* @return 返回计算结果
*/
const stringDifference = (a = '', b = '', base = 10) => {
// 结果
let result = '';
// 借位标记
let carry = 0;
// 设置 a、b 的长度,方便逆序遍历
let aIndex = a.length - 1, bIndex = b.length - 1;
while (aIndex >= 0 || bIndex >= 0) { // a 或 b 还有位可以相减
// 判断是否需要借位
let reduce = 0;
if ((+a[aIndex] || 0) < (+b[bIndex] || 0)) {
reduce = (+a[aIndex] || 0) + base - (+b[bIndex]);
carry = 1;
} else {
reduce = (+a[aIndex] || 0) - (+b[bIndex] || 0) - carry;
carry = 0;
}
// 计算最终结果
result = reduce % base + result;
// 移位后往更高位靠
aIndex--;
bIndex--;
}
// 返回最终结果
return result;
};
四 十进制转二进制
注意!这里的十进制转二进制包括:
- 正整数 [0, n]
- 负整数 [n, 0)
- 小数 (0, 1)
- 小数 (1, n)
不包括负小数 不包括负小数 不包括负小数 重要的事说三遍,如果你需要,私聊我,看心情补补了~
/**
* @name stringAddtion
* @description 字符串求和
* @param {string} a 加数一
* @param {string} b 加数二
* @param {any} base 基数(默认十进制)
* @return 返回计算结果
*/
const stringAddtion = (a = '', b = '', base = 10) => {
// 结果
let result = '';
// 进位标记
let carry = 0;
// 设置 a、b 的长度,方便逆序遍历
let aIndex = a.length - 1, bIndex = b.length - 1;
while (aIndex >= 0 || bIndex >= 0) { // a 或 b 还有位可以相加
// aIndex bIndex可能为负数值,需要转化为 0
let sum = (+a[aIndex] || 0) + (+b[bIndex] || 0) + carry;
// 是否需要进位
carry = sum >= base ? 1 : 0;
// 计算最终结果
result = sum % base + result;
// 移位后往更高位靠
aIndex--;
bIndex--;
}
// 如果计算完毕后还有进位,那么前面 + 1
if (carry) {
result = '1' + result;
}
// 返回最终结果
return result;
};
/*
* 英文荒漠 jsliang 查找的缩写
* EQU - 等于:equal
* NEQ - 不等于:not equal
* LSS - 小于:less than
* LEQ - 小于或等于:equal or less than
* GTR - 大于:greater than
* GEQ - 大于或等于:equal or greater than
*/
/**
* @name integerLEQZero
* @description 大于或者等于 0 的整数转换为二进制
* @param {number} number
* @return 二进制字符串
*/
const integerGEQZero = (number) => {
let result = '';
while (number >= 1) {
result = number % 2 + result;
number = Math.floor(number / 2);
}
return result;
};
/**
* @name integerLSSZero
* @description 小于 0 的整数转换为二进制
* @param {number} number
* @return 二进制字符串
*/
const integerLSSZero = (number) => {
let result = '';
// 转正
number = -number;
// 求二进制并取反
while (number >= 1) {
if (number % 2 === 1) {
result = '0' + result;
} else {
result = '1' + result;
}
number = Math.floor(number / 2);
}
// 补全
if (result.length < 8) {
result = '1'.repeat(8 - result.length) + result;
}
// 加一
result = stringAddtion(result, '1', 2);
return result;
};
/**
* @name floorGTROne
* @description 大于 1 的小数转二进制
* @param {number} number
* @return 二进制字符串
*/
const floorGTROne = (number) => {
let result = '';
const integer = Math.floor(number);
const decimal = number - integer;
result += integerGEQZero(integer) + '.' + floorBetweenZeroOne(decimal).split('.')[1];
return result;
};
/**
* @name floorBetweenZeroOne
* @description 大于 0 小于 1 的小数转二进制
* @param {number} number
* @return 二进制字符串
*/
const floorBetweenZeroOne = (number) => {
let result = '';
while (
number != 0
&& number != 1 // 取值到 0 或者 1 为止
&& result.length < 32 // 或者长度达到需要
) {
// 取小数部分
if (number > 1) {
number = number - 1;
}
result = result + Math.floor(number * 2);
number = number * 2;
}
result = '0.' + result;
return result;
};
/**
* @name decimalToBinary
* @description 正负整数转二进制
* @param {any} number 需要转换的数字
* @return 二进制字符串
*/
const decimalToBinary = (number) => {
let result = '';
if (Number.isInteger(number) && number >= 0) { // 正整数
result = integerGEQZero(number);
} else if (Number.isInteger(number) && number < 0) { // 负整数
result = integerLSSZero(number);
} else if (!Number.isInteger(number) && number > 0 && number < 1) { // 正小数(不带整数位)
result = floorBetweenZeroOne(number);
} else if (!Number.isInteger(number) && number > 1) { // 正小数(带整数位)
result = floorGTROne(number);
}
return result;
};
// 正整数转二进制 [0, n]
console.log(decimalToBinary(10)); // 1010
console.log(decimalToBinary(42)); // 101010
// 负整数转二进制 [n, 0)
console.log(decimalToBinary(-10)); // 11110110
console.log(decimalToBinary(-42)); // 11010110
// 小数转二进制 (0, 1)
console.log(decimalToBinary(0.125)); // 0.001
console.log(decimalToBinary(0.8125)); // 0.1101
// 小数转二进制 (1, n)
console.log(decimalToBinary(6.125)) // 110.001
console.log(decimalToBinary(173.8125)) // 10101101.1101
五 二进制转十进制
注意!这里的二进制转十进制包括:
- 正整数 [0, n]
- 负整数 [n, 0)
- 小数 (0, 1)
- 小数 (1, n)
不包括负小数 不包括负小数 不包括负小数 重要的事说三遍,如果你需要,私聊我,看心情补补了~
其实就是上面代码的计算的逆推……但是逆推也是挺累的好伐 -_-
/**
* @name stringDifference
* @description 字符串求差
* @param {string} a 减数一
* @param {string} b 减数二
* @param {any} base 基数(默认十进制)
* @return 返回计算结果
*/
const stringDifference = (a = '', b = '', base = 10) => {
// 结果
let result = '';
// 借位标记
let carry = 0;
// 设置 a、b 的长度,方便逆序遍历
let aIndex = a.length - 1, bIndex = b.length - 1;
while (aIndex >= 0 || bIndex >= 0) { // a 或 b 还有位可以相减
// 判断是否需要借位
let reduce = 0;
if ((+a[aIndex] || 0) < (+b[bIndex] || 0)) {
reduce = (+a[aIndex] || 0) + base - (+b[bIndex]);
carry = 1;
} else {
reduce = (+a[aIndex] || 0) - (+b[bIndex] || 0) - carry;
carry = 0;
}
// 计算最终结果
result = reduce % base + result;
// 移位后往更高位靠
aIndex--;
bIndex--;
}
// 返回最终结果
return result;
};
/*
* 英文荒漠 jsliang 查找的缩写
* EQU - 等于:equal
* NEQ - 不等于:not equal
* LSS - 小于:less than
* LEQ - 小于或等于:equal or less than
* GTR - 大于:greater than
* GEQ - 大于或等于:equal or greater than
*/
/**
* @name integerLEQZero
* @description 大于或者等于 0 的二进制转换为整数
* @param {string} binary
* @return 整数
*/
const integerGEQZero = (binary) => {
let result = 0;
const index = binary.indexOf('1');
const newBinary = binary.slice(index).split('').reverse('').join(''); // 截取 1 后面的,然后从最后往前取
for (let i = 0; i < newBinary.length; i++) {
result += newBinary[i] * Math.pow(2, i);
}
return result;
};
/**
* @name integerLSSZero
* @description 小于 0 的二进制转换为整数
* @param {string} binary
* @return 整数
*/
const integerLSSZero = (binary) => {
let result = 0;
// 减一
binary = stringDifference(binary, '1', 2);
let reverseBinary = ''; // 取反
for (let i = 0; i < binary.length; i++) {
if (binary[i] === '0') {
reverseBinary += '1';
} else {
reverseBinary += '0';
}
}
result = -(integerGEQZero(reverseBinary));
return result;
};
/**
* @name floorGTROne
* @description 大于 1 的二进制转小数
* @param {string} binary
* @return 小数
*/
const floorGTROne = (binary) => {
let result = 0;
const [integer, decimal] = binary.split('.');
result = integerGEQZero(integer) + floorBetweenZeroOne('0.' + decimal);
return result;
};
/**
* @name floorBetweenZeroOne
* @description 大于 0 小于 1 的二进制转小数
* @param {string} binary
* @return 小数
*/
const floorBetweenZeroOne = (binary) => {
let result = 0;
binary = binary.replace('.', ''); // 去掉小数点
for (let i = 0; i < binary.length; i++) {
result += Number(binary[i]) * Math.pow(2, -i);
}
return result;
};
/**
* @name binaryToDecimal
* @description 二进制转十进制
* @param {string} binary
* @return 十进制整数
*/
const binaryToDecimal = (binary) => {
let result = 0; // 设置结果集
if (!binary.includes('.')) { // 不带小数点的
// 不足 8 位则补全 8 位
if (binary.length < 8) {
binary = '0'.repeat(8 - binary.length) + binary;
}
if (binary[0] === '0') { // 首位为 0 则是正整数 [0, n]
result = integerGEQZero(binary);
} else if (binary[0] === '1') { // 首位为 1 则是负整数 [n, 0)
result = integerLSSZero(binary);
}
} else { // 带小数点的
if (binary.split('.')[0] === '0') { // 如果整数部位只有 0 (0, 1)
result = floorBetweenZeroOne(binary);
} else { // 如果整数部位大于 0 (1, n)
result = floorGTROne(binary);
}
}
return result;
};
// 二进制转正整数 [0, n]
console.log(binaryToDecimal('1010')); // 10
console.log(binaryToDecimal('101010')); // 42
// 二进制转负整数 [n, 0)
console.log(binaryToDecimal('11101011')); // -21
console.log(binaryToDecimal('11110110')); // -10
console.log(binaryToDecimal('11010110')); // -42
// 二进制转小数 (0, 1)
console.log(binaryToDecimal('0.001')); // 0.125
console.log(binaryToDecimal('0.1101')); // 0.8125
// 二进制转小数 (1, n)
console.log(binaryToDecimal('110.001')); // 6.125
console.log(binaryToDecimal('10101101.1101')); // 173.8125
六 总结
以上,就是本次的探索了~
是的,不带任何讲解……
小伙伴们可以结合《加减危机》进行食用:
如果小伙伴们发现文章有误,欢迎指正~
不折腾的前端,和咸鱼有什么区别!

jsliang 会每天更新一道 LeetCode 题解,从而帮助小伙伴们夯实原生 JS 基础,了解与学习算法与数据结构。
浪子神剑 会每天更新面试题,以面试题为驱动来带动大家学习,坚持每天学习与思考,每天进步一点!
扫描上方二维码,关注 jsliang 的公众号(左)和 浪子神剑 的公众号(右),让我们一起折腾!
jsliang 的文档库 由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/LiangJunron…上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。