• ​JavaScript中的多种进制与进制转换 ​


    进制介绍

    JavaScript 中提供的进制表示方法有四种:十进制、二进制、十六进制、八进制。
    对于数值字面量,主要使用不同的前缀来区分:

    • 十进制(Decimal):
      取值数字 0-9;不用前缀。
    • 二进制(Binary):
      取值数字 0 和 1 ;前缀 0b 或 0B
    • 十六进制(Hexadecimal):
      取值数字 0-9 和 a-f ;前缀 0x 或 0X
    • 八进制(Octal):
      取值数字 0-7 ;前缀 0o 或 0O (ES6规定)。

    需要注意的是,非严格模式下浏览器支持:如果有前缀0并且后面只用到 0-7 八个数字的数值时,该数值视为八进制;但如果前缀0后面跟随的数字中有8或者9,则视为十进制。
    严格模式下,如果数字加前缀0,则报错:Uncaught SyntaxError: Decimals with leading zeros are not allowed in strict mode。
    各进制的数值,如果取值数字超过给定的范围,则会报错:Uncaught SyntaxError: Invalid or unexpected token。

    在JavaScript内部的默认情况下,二进制、十六进制、八进制字面量数值,都会自动转为十进制进行运算。

    1. 0x22 // 34
    2. 0b111 // 7
    3. 0o33 // 27
    4. 0x22 + 0b111 // 41
    5. 0o33 + 12 // 39
    6. (0x33).toString() // 51
    7. (0x33).valueOf() // 51

    除了十进制是Javascript默认的数字进制以外,其他三种进制方式平时使用较少,主要在处理底层数据、字节编码或者位运算等时候才会碰到。

    进制转换

    本文将主要讨论进制转换时的问题。
    JavaScript 提供了原生函数,进行十进制与其他各进制之间的相互转换。
    其中,从其他进制转换成十进制,有三种方式:parseInt()Number()+(一元运算符)。这三种方式都只能转换整数。
    从十进制转换成其他进制,可以使用 Number.prototype.toString()。支持小数。

    parseInt(str, radix)

    第一个参数是需要解析的字符串;其他进制不加前缀。
    第二个参数是一个进制基数,表示转换时按什么进制来理解这个字符串,默认值10,表示转十进制。
    第二个参数如果非数字,则自动转数字,如无法转称数字则忽略该参数;是数字时,必须是 2-36 的整数,超出该范围,返回 NaN

    1. parseInt('1111', 2) // 15
    2. parseInt('1234', 8) // 668
    3. parseInt('18af', 16) // 6319
    4. parseInt('1111') // 1111

    如果不传入第二参数,则 parseInt 会默认使用十进制来解析字符串;但是,如果字符串以 0x 开头,会被认为是十六进制数。
    而其他进制的字符串,0o21(八进制)0b11(二进制) 不会以该进制基数自动转换,而是得到 0
    所以,在使用 parseInt 进行进制转换时,为了保证运行结果的正确性和稳定性,第二个参数不能省略

    1. parseInt('0x21') // 33
    2. parseInt('0o21') // 0
    3. parseInt('0b11') // 0
    4. parseInt('111', 'add') // 111
    5. parseInt('111', '787') // NaN

    如果需要解析的字符串中存在对于当前进制基数无效的字符,则会从最高位取有效字符进行转换,没有效字符则返回NaN。

    1. parseInt('88kk', 16) // 136=== 0x88
    2. parseInt('kk', 16) // NaN

    Number()

    可以把字符串转为数字,支持其他进制的字符串,默认转成十进制数字。
    字符串中如果存在无效的进制字符时,返回 NaN
    记住,需要使用进制前缀,0b0o0x

    1. Number('0b11100') // 28
    2. Number('0o33') // 27
    3. Number('0x33') //51
    4. Number('0x88kk') // NaN

    +(一元运算符)

    与 Number() 一样,可以把字符串转为数字,支持其他进制的字符串,默认转成十进制数字。
    字符串中如果存在无效的进制字符时,返回 NaN
    也需要使用进制前缀。

    1. +'0b11100' // 28
    2. +'0o33' // 27
    3. +'0x33' //51
    4. +'0x88kk' // NaN

    可以看到,基本和 Number() 是一样的,都在本质上是对数字的一种转换处理。

    Number.prototype.toString(radix)

    它支持传入一个进制基数,用于将数字转换成对应进制的字符串,它支持转换小数
    未指定默认值为 10,基数参数的范围 2-36,超过范围,报错:RangeError。

    1. 15..toString(2) // 1111
    2. 585..toString(8) // 1111
    3. 4369..toString(16) // 1111
    4. (11.25).toString(2) // 1011.01

    自定义转换

    除了这些原生函数以外,也可以自己实现进制数字之间的转换函数。
    根据相应的规则,就可以实现十进制与二进制、十六进制之间的转换的一些方法。

    十进制与十六进制转换

    以下代码是针对整数在十进制与十六进制之间的转换,根据基本规则进行换算。
    十六进制是以 0-9a-f 进行描述数字的一种方式,其中 0-9 取本身数字的值,而 a-f 则取 10-15 的值。
    且字母不区分大小写。

    1. function int2Hex (num = 0) {
    2. if (num === 0) {
    3. return '0'
    4. }
    5. const HEXS = '0123456789abcdef'
    6. let hex
    7. while (num) {
    8. hex = HEXS.charAt(num % 16) + hex
    9. num = Math.floor(num / 16)
    10. }
    11. return hex
    12. }
    1. function hex2Int (hex = '') {
    2. if (typeof hex !== 'string' || hex === '') {
    3. return NaN
    4. }
    5. const hexs = [...hex.toLowerCase()]
    6. let resInt = 0
    7. for (let i = 0; i < hexs.length; i++) {
    8. const hv = hexs[i]
    9. let num = hv.charCodeAt() < 58 ? +hv : ((code - 97) + 10)
    10. resInt = resInt * 16 + num
    11. }
    12. return resInt
    13. }

    如果要转换八进制,实际上与十六进制很类似,只需根据八进制的数值范围进行部分改动即可。八进制一般使用非常少,不单独列出。

    下面将重点介绍二进制转换的相关知识,包括小数的二进制表示与转换。

    十进制和二进制转换

    在十进制与二进制的转换中,我们将考虑小数,理解小数是如何在这两者之间进行转换。
    先选定一个数字,比如:11.125 ,我们看下该数字在二进制里的表示:

    (11.125).toString(2) // 1011.001
    

    可以看到,11.125 的二进制表示为:1011.001。下面将以这个数字为例进行转换操作。

    十进制数字转换成二进制

    首先需要了解的是,二进制小数表示方法是如何得来的:

    • 整数 部分,用二进制表示可以如此计算,数字 11:
      11 / 2 ———— 1
      5 / 2 ———— 1
      2 / 2 ———— 0
      1 / 2 ———— 1
      整数部分的规则,得到的结果是 从下往上,倒着排 1011 就是二进制的 11。

    • 小数 用二进制表示可以如此计算,小数 0.125
      例如十进制的 0.125
      0.125 × 2 = 0.25 ———— 0
      0.25 × 2 = 0.5 ———— 0
      0.5 × 2 = 1 ———— 1
      只有等于1时才结束,如果结果不等于1将会一直循环下去。
      小数部分的规则,得到的结果是 从上往下,顺着排 0.001 就是二进制的 0.125

      整数 + 小数,所以 11.125 的二进制表示方式:1011.001
      根据以上整数和小数分开计算的规则,就可以得出十进制转二进制的函数,如下:

      1. function c10to2 (num) {
      2. // 整数
      3. const numInteger = Math.floor(num)
      4. // 小数
      5. const numDecimal = num - numInteger
      6. let integers = []
      7. if (numInteger === 0) {
      8. integers = ['0']
      9. } else {
      10. let integerVal = numInteger
      11. while(integerVal !== 1) {
      12. integers.push(integerVal % 2 === 0 ? '0' : '1')
      13. integerVal = Math.floor(integerVal / 2)
      14. }
      15. integers.push('1')
      16. }
      17. const resInteger = integers.reverse().join('')
      18. let decimals = []
      19. if (numDecimal) {
      20. let decimalVal = numDecimal
      21. // 最多取49位的长度
      22. let count = 49
      23. while (decimalVal !== 1 && count > 0) {
      24. decimalVal = decimalVal * 2
      25. if (decimalVal >= 1) {
      26. decimals.push('1')
      27. if (decimalVal > 1) {
      28. decimalVal = decimalVal - 1
      29. }
      30. } else {
      31. decimals.push('0')
      32. }
      33. count--
      34. }
      35. }
      36. const resDecimal = decimals.join('')
      37. return resInteger + (resDecimal ? ('.' + resDecimal) : '')
      38. }

      小数在转换成二进制时,会存在无限循环的问题,上面的代码里截取了前49个值。
      所以,这里就会引出了一个问题,就是常见的一个数字精度问题:0.1 + 0.2 != 0.3

    0.1+ 0.2 != 0.3

    直接看一下 0.1 转二进制:
    0.1 × 2 = 0.2
    0.2 × 2 = 0.4
    0.4 × 2 = 0.8
    0.8 × 2 = 1.6
    0.6 × 2 = 1.2
    0.2 × 2 = 0.4 // 循环开始
    0.4 × 2 = 0.8
    0.8 × 2 = 1.6
    0.6 × 2 = 1.2
    ...
    ...
    无限循环

    0.2 转二进制:
    0.2 × 2 = 0.4
    0.4 × 2 = 0.8
    0.8 × 2 = 1.6
    0.6 × 2 = 1.2
    0.2 × 2 = 0.4 // 循环开始
    0.4 × 2 = 0.8
    0.8 × 2 = 1.6
    0.6 × 2 = 1.2
    ...
    ...
    无限循环

    因为无法得到1,可以发现有限十进制小数, 0.1 转换成了无限二进制小数 0.00011001100...0.2 转成了 0.001100110011...
    由于无限循环,必然会导致精度丢失,正好 0.1 + 0.2 计算得到的数字在丢失精度后的最后一位不为0,所以导致结果为:0.30000000000000004
    如果截取精度后最后一位为0,那自然就不存在结果不相等的情况,如 0.1 + 0.6 === 0.7,事实上,0.1和0.6转二进制后都会丢失精度,但截取到的数值都是0,所以相等。
    同样不相等的还设有 0.1 + 0.7 !== 0.8等等。
    所以是计算时转二进制的精度丢失,才导致的 0.1 + 0.2 !== 0.3

    在 JavaScript 中所有数值都以 IEEE-754 标准的 64 bit 双精度浮点数进行存储的。
    IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持53位二进制位。
    因浮点数小数位的限制而需要先截断二进制数字,再转换为十进制,所以在进行算术计算时会产生误差。

    这里能看到,如果十进制小数要被转化为有限二进制小数,那么它计算后的小数第一位数必然要是 5 结尾才行(因为只有 0.5 × 2 才能变为整数)。

    二进制数字转换成十进制

    方法是:将二进制分成整数和小数两部分,分别进行转换,然后再组合成结果的十进制数值。

    1. 整数部分:这里直接使用 parseInt 函数,parseInt('1011', 2) => 11

    2. 小数部分:如 1011.001 的小数位 001,使用下表的计算方式。
      小数部分|0|0|1
      --|--|--|--
      基数的位数次幂|2-1|2-2|2^-3
      每位与基数乘积|0 × (2^-1)|0 × (2-2)|1×(2-3)
      每位乘积结果|0|0|0.125

      最后的结果是每位乘积结果相加:0+0+0.125 = 0.125

    整数与小数合起来,就得到了 1011.001 的十进制数字:11.125

    根据规则,代码实现如下所示:

    1. function c2To10 (binaryStr = '') {
    2. if (typeof binaryStr !== 'string' || binaryStr === '') {
    3. return NaN
    4. }
    5. const [ binIntStr, binDecStr ] = binaryStr.split('.')
    6. let binDecimal = 0
    7. if (binDecStr) {
    8. binDecimal = [...binDecStr].reduce((res, val, index) => {
    9. res += Number(val) * (2 ** (-(index + 1)))
    10. return res
    11. }, 0)
    12. }
    13. return parseInt(binIntStr, 2) + binDecimal
    14. }
  • 相关阅读:
    武汉智能网联道路智能化建设规范
    原文远知行COO张力加盟逐际动力 自动驾驶进入视觉时代?
    【OPENVX】对象基本使用之vx_pyramid
    项目代驾(Delegate):新式灵活的协作模式
    LocaSpaceViewer:自定义图源与无偏影像下载
    【UE 网络】专用服务器和多个客户端加入游戏会话的过程,以及GameMode、PlayerController、Pawn的创建流程
    11个Redis系列高频面试题,哪些你还不会?
    2024社招面经_存储&DB&广告架构方向
    Java基础知识讲解-ArrayList类
    ARM 账号注册报错 The claims exchange ‘Salesforce-UserWriteUsingEmail‘
  • 原文地址:https://blog.csdn.net/jh035/article/details/128128178