进制(一):js 中的各种进制转换

最近项目里涉及到了进制转换,顺带回忆一下二进制。。

背景

javascript中很少会使用到二进制做处理, 但是最近开发时遇到了一个场景 :

admin需要做一个对接 google ads 的工具, 以简化 marketing team 在做广告投放时做的一些重复性操作. 而其中涉及到一个流程: 将图片/zip/视频上传到 amazon aws s3 存储, 后端再上传到 google ads . 其中有一种图片格式是 wbmp , 这种格式浏览器并不支持, 所以需要转换, 其中就涉及到了 js 中的二进制转换。

计算机中以字节为单位存储和解释信息,规定一个字节由八个二进制位(比特)构成,即 1 个字节等于 8 个比特(1Byte=8bit)

二进制

先复习一下二进制, 数字在计算机中使用二进制表示, 有两个注意点:

  1. 有符号位: 最高位不表示数值, 表示符号
  2. 补码表示: 以原码形式表示数值

原码

以一个字节, 即 8 个二进制位为例, 第一位为符号位, 剩余为数字位

用原码, 可表示范围为: 11111111 ~ 00000000 ~ 01111111

按等比数列可得: - (2^7 - 1) ~ 0 ~ 2^7 - 1

即: -127 ~ 0 ~ 127

原码对人的识别友好, 但是在用原码计算时会出现问题:

1
00000001(1) + 10000001(-1) = 10000010(-2)

这不是我们想要的

反码

反码是将数字位取反获得的, 其中规定:

  • 正数反码, 为本身
  • 负数反码, 数字位按位取反

即 11111111 的反码为 10000000

如果反码相加的话, 结果似乎是对的:

1
2
3
4
5
6
-127: 原码: 11111111, 反码: 10000000
+127: 原码: 01111111, 反码: 01111111

反码相加: -127 + 127 = 10000000 + 01111111 = 11111111

11111111 转换为原码为: 10000000 (-0)

虽然结果是对的, 但是存在 -0 的情况, -0 和+0 是相等的, 没有意义

补码

还有一种表示形式, 补码

  • 正数补码, 本身
  • 负数补码, 反码的基础上 +1

网络上很多文章提到, 补码是为了解决反码相加得出正负 0 而提出的, 虽然能说得通, 但是

在既不能证实也不能证伪的情况下,只能存疑 — 周孝正

我们不讨论反码补码因为什么出现的, 只说明反码补码解决了什么问题

补码确实可以解决相加正负 0 的问题

1
2
3
4
5
6
-127: 原码: 11111111, 反码: 10000000  补码: 10000001
+127: 原码: 01111111, 反码: 01111111 补码: 01111111

补码相加: -127 + 127 = 10000001 + 01111111 = 100000000

100000000 最高位溢出, 剩余8位刚好为0

js 中图片转换

怎么用 js 进行进制转换

  1. 表示形式

各进制数值在 js 中的表示时的前缀

  • 2 进制: 0b
  • 8 进制: 0
  • 10 进制: 无前缀
  • 16 进制: 0x

其中要注意的是符号位还是用 +- 表示, 如十进制 -12

表示为 2 进制是 -0b1100 ,

表示为 8 进制是 -014 ,

表示为 16 进制是 -0xc ,

  1. 转换方法

javascript 进制转换的方法主要有两个:

  • parseInt: 其他进制转 10 进制
1
2
3
4
5
6
7
8
9
10
11
12
// 按2进制解析
parseInt('-0b1100', 2) // -12
// 按2进制解析
parseInt('-1100', 2) // -12
// 按8进制解析
parseInt('-014', 8) // -12
// 按8进制解析
parseInt('-14', 8) // -12
// 按16进制解析
parseInt('-0xc', 16) // -12
// 按16进制解析
parseInt('-c', 16) // -12
  • toString : 将数字以任意进制表示
1
2
3
4
5
6
7
8
9
// 假定有一个2进制数
let num = -0b1100

// 以8进制表示
num.toString(8) // "-14"
// 以16进制表示
num.toString(10) // "-12"
// 以16进制表示
num.toString(16) // "-c"

精度问题

js 的所有数字,都是使用 64 位双精度浮点型来表示。并且是指数的形式。

转换成二进制后,就会有除不尽的问题:

1
2
3
const a = 0.2;
a.toString(2)
// 0.0001100110011001100110011001100110011001100110011001101

可以看到 0011 一直在重复,我们不能用有限的空间存储无限长度的数。

我们可以先转换成字符串,再按位数相加,大数相加也是一个道理。