2020-04-24

Javascript-c...

写在前面

  • 简单记录一下,Javascript parseInt+parseFloat 内部实现,并实现 convertStringToNumber

实践准备

  • 首先我们需要简单梳理一下我们的实现过程,再根据 ECMAScript-262 标准完善实现
  • input: @params: { str } 输入需要转换的字符串, { radix } 转换的指定基数
  • 对 input 的简单处理 StringNumericLiteral
    • 规格化 str
    • StringNumericLiteral BNF
      • 将 str 中可能出现的 StrWhiteSpace 去掉
        • StrWhiteSpace BNF
          • LineTerminator BNF
    • 判断 radix 是否合法,仅支持 Decimal / BinaryInteger / OctalInteger / HexInteger radix
      • Decimal: 10
      • BinaryInteger: 2
      • OctalInteger: 8
      • HexInteger: 16
  • 简单算法处理过程
    • 十进制数
      • StrDecimalLiteral BNF
      • 处理 Infinity 情况
      • 符号位处理
      • 小数点
      • 科学计数法
    • 二进制数
      • BinaryInteger
    • 八进制数
      • OctalInteger
    • 十六进制数
      • HexInteger
  • output: return number

详细实践

  • 根据上面的实践准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
* @params: { str } 输入需要转换的字符串, { radix } 转换的指定基数
* return: number
*/
function convertStringToNumber(str, radix) {
const noWhiteSpaceStr = replaceWhiteSpaceInStr(str) /** 将 str 中的 whitespace 进行匹配 */
const checkedRadixStr = formatStrByRadix(str, radix) /** 将 str 根据指定基数进行转换 */
let resNum = 0
switch (radix) {
case 10:
resNum = converStringToDeciaml(str) /** 将 str 转成十进制 */
break;
case 2:
resNum = converStringToBinaryInteger(str) /** 将 str 转成二进制*/
break;
case 8:
resNum = converStringToOctalInteger(str) /** 将 str 转成八进制*/
break;
case 16:
resNum = converStringToHexIntegers(str) /** 将 str 转成十六进制*/
break;
default:
return NaN
}
return resNum
}
  • 将 str 中的 whitespace 进行匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /*
    * @params: { str } 输入需要替换的字符串
    * return: resStr 无 whitespace 字符串
    */
    function replaceWhiteSpaceInStr(str) {
    let resStr = str.replace(/\s*/g, '') // 去除空格
    // resStr = resStr.replace(/^[\u000A|\u000D|\u2028|\u2029]/g, '') // 去除 LineTerminator unicode输入方式
    resStr = resStr.replace(/[\r|\n]/g, '') // 去除换行
    return resStr
    }
    • 单元测试
      • 单元测试截图
  • 将 str 根据指定基数进行转换

    • 正则表达式匹配 Number 字面量

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      /*
      * @params: { str } 输入需要替换的字符串, { radix } 转换的指定基数
      * return: resStr 根据指定基数转换过得字符串
      */
      function formatStrByRadix(str, radix) {
      let resStr = ''
      let testReg = null
      switch (radix) {
      case 10:
      testReg = /^((0)|([1-9][0-9]*))?.?([0-9]*)((e|E)?(\+|\-)?([0-9]*))?/
      break;
      case 2:
      // 根据 ECMA-262 是 /^0(b|B)(0|1)+$/
      // 但是我在 Chrome 浏览器上测了,二进制没有 'b' or 'B'
      testReg = /^(0|1)+/
      break;
      case 8:
      // 根据 ECMA-262 是 /^0(O|o)[0-7]+$/
      // 但是我在 Chrome 浏览器上测了,二进制没有 'o' or 'O'
      testReg = /^[0-7]+/
      break;
      case 16:
      // 根据 ECMA-262 是 /^0(x|X)([0-9a-fA-F])+/
      // 但是我在 Chrome 浏览器自测中发现,没有表示符 'x' or 'X'也可
      testReg = /^0(x|X)?([0-9a-fA-F])+/
      break;
      default:
      console.log(`radix: ${radix}, str: ${str} illegal radix`)
      return false
      }
      resStr = (testReg.exec(str) && testReg.exec(str)['0']) || 'convert fail'
      console.log(`radix: ${radix}, ${str} --> resStr: `, resStr)
      }
      formatStrByRadix('001010101', 3)
      formatStrByRadix('001010101', 2)
      formatStrByRadix('001010101', 8)
      formatStrByRadix('0b01010101', 2)
      formatStrByRadix('012345677', 8)
      formatStrByRadix('012345677', 2)
      formatStrByRadix('0o12345677', 8)
      formatStrByRadix('1.01E+23', 10)
      formatStrByRadix('.012345677', 10)
      formatStrByRadix('.012345677', 10)
      formatStrByRadix('0123acfACF', 16)
      formatStrByRadix('0x123acfACF', 16)
      formatStrByRadix('0x123acfACFG', 16)
    • 单元测试

      • 单元测试
  • 根据指定基数进行相应算法转换

    • 二进制:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      // 将 str 转成二进制
      function converStringToBinaryInteger(str, radix) {
      return strMultipleRadix(str, radix)
      }

      /*
      * @params: { str } 输入需要替换的字符串, { radix } 转换的指定基数
      * return: resStr 乘以指定基数转换过得字符串
      */
      function strMultipleRadix(str, radix) {
      let tempStrArr = str.split('')
      let res = 0
      for (let i = tempStrArr.length - 1; i > 0; i --) {
      res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
      }
      return res
      }
      converStringToBinaryInteger('001010101', 2)
      • 单元测试
        • 单元测试截图
    • 八进制

      • 同上

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        // 将 str 转成八进制
        function converStringToBinaryInteger(str, radix) {
        return strMultipleRadix(str, radix)
        }

        /*
        * @params: { str } 输入需要替换的字符串, { radix } 转换的指定基数
        * return: resStr 乘以指定基数转换过得字符串
        */
        function strMultipleRadix(str, radix) {
        let tempStrArr = str.split('')
        let res = 0
        for (let i = tempStrArr.length - 1; i > 0; i --) {
        res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
        }
        return res
        }
        converStringToBinaryInteger('001010101', 2)
      • 单元测试

        • 单元测试截图
    • 十六进制

      • 需要对 a-f A-F 进行判断,别忘了+10

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        // 将 str 转成十六进制
        function converStringToBinaryInteger(str, radix) {
        return strMultipleRadix(str, radix)
        }

        /*
        * @params: { str } 输入需要替换的字符串, { radix } 转换的指定基数
        * return: resStr 乘以指定基数转换过得字符串
        */
        function strMultipleRadix(str, radix) {
        let tempStrArr = str.split('')
        let res = 0
        for (let i = tempStrArr.length - 1; i > 0; i--) {
        if (radix === 16) {
        if ((tempStrArr[i].codePointAt(0) - '0'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0) < 10)) {
        res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
        } else if ((tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) < 6)) {
        console.log('(tempStrArr[i].codePointAt(0)a', tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0))
        res += (tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) + 10) * Math.pow(radix, tempStrArr.length - i - 1)
        } else if ((tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) < 6)) {
        console.log('(tempStrArr[i].codePointAt(0)A', tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0))
        res += (tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) + 10) * Math.pow(radix, tempStrArr.length - i - 1)
        }
        } else {
        res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
        }
        }
        return res
        }
        console.log(converStringToBinaryInteger('001010101', 16))
        console.log(converStringToBinaryInteger('00101acfAFF', 16))
      • 单元测试

        • 单元测试截图
  • 最终二/八/十六进制根据指定基数进行相应算法转换方法为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /*
    * @params: { str } 输入需要替换的字符串, { radix } 转换的指定基数
    * return: resStr 乘以指定基数转换过得字符串
    */
    function strMultipleRadix(str, radix) {
    let tempStrArr = str.split('')
    let res = 0
    for (let i = tempStrArr.length - 1; i > 0; i--) {
    if (radix === 16) {
    if ((tempStrArr[i].codePointAt(0) - '0'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0) < 10)) {
    res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
    } else if ((tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) < 6)) {
    res += (tempStrArr[i].codePointAt(0) - 'a'.codePointAt(0) + 10) * Math.pow(radix, tempStrArr.length - i - 1)
    } else if ((tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) >= 0) && (tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) < 6)) {
    res += (tempStrArr[i].codePointAt(0) - 'A'.codePointAt(0) + 10) * Math.pow(radix, tempStrArr.length - i - 1)
    }
    } else {
    res += (tempStrArr[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, tempStrArr.length - i - 1)
    }
    }
    return res
    }

    转换成十进制

  • 实践思路

    1. 判断正负符号位
    2. 判断是否为 Infinity
    3. 判断是否为 整数
      1. 整数
        1. 是否为科学计数法表示
          1. 是,通过指数位置分成两部分:整数部分+指数部分
          2. 否,只需处理整数部分
      2. 小数
        1. 是否为科学计数法表示
          1. 是,通过分割成小数点到指数,分成三部分:整数部分+小数部分+指数部分
          2. 否,通过小数点位置,分成两部分:整数部分+小数部分
  • 代码大致为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function converStringToDeciaml(str) {
// console.log('str-=-=--=-==-=', str)
const sign = getSign(str)
const strFormatBySign = formatStrBySign(str)
if (isInfinity(strFormatBySign)) return (1 / 0) * sign // 如果为 Inifity,乘以符号位输出Infinity
const numberObject = splitStr(strFormatBySign)
const {int, float, exponentSign, exponent} = numberObject
// console.log('numberObject', numberObject)
const resInt = calculateInt(int) // 计算整数部分
// console.log('resInt', resInt)
const resFloat = calculateFloat(float) // 计算小数部分
// console.log('resFloat', resFloat)
const resExponent = calculateExponent(resInt, resFloat, exponentSign, exponent) // 计算(整数+小数)* 指数部分
const res = sign * resExponent // 最后乘以符号位
return res
}

判断正负,并根据符号位格式化字符串

1
2
3
4
5
6
7
8
9

// 获取符号位
function getSign(str) {
// 如果S的首字符为'-'
if(str.indexOf('-') == 0) {
return -1
}
return 1
}

根据符号位格式化字符串(移除字符号位)

1
2
3
4
5
6
7
8
// 根据符号位格式化字符串
function formatStrBySign(str) {
// 如果 str 的首字符为‘+’或'-',则移除首字符
if (str.indexOf('-') == 0 || str.indexOf('+') == 0) {
str = str.substring(1, str.length)
}
return str
}

判断是否为Infinity

1
2
3
4
5
6
// 是否为 Infinity
function isInfinity(str) {
const testReg = /^Infinity/
let resReg = testReg.exec(str)
return resReg && resReg[0]
}

切割字符串,分成整数+小数+指数位符号+指数部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 切割字符串
/*
* @params: { str } 输入需要切割的字符串
* return: res: Object {
* int: 整数位,
* float: 小数位,
* exponentSign: 指数位符号,
* exponent: 指数位
* }
*/
function splitStr(str) {
const testReg = /^((0)|([1-9][0-9]*))?.?([0-9]*)(e|E)?((\+|\-)?([0-9]*))?/
let resReg = testReg.exec(str)
let res = {
int: resReg['1'] || 0,
float: resReg['4'] || 0,
exponentSign: resReg['7'] || '+',
exponent: resReg['8'] || 0
}
return res
}

整数部分运算

1
2
3
4
5
6
7
8
9
10

// 整数部分运算
function calculateInt(str) {
let res = 0
const radix = 10
for (let i = str.length - 1; i >= 0; i--) {
res += (str[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, str.length - i - 1)
}
return res
}

小数部分运算

1
2
3
4
5
6
7
8
9
10

// 小数部分运算
function calculateFloat(str) {
let res = 0
const radix = 10
for (let i = 0; i < str.length; i++) {
res += (str[i].codePointAt(0) - '0'.codePointAt(0)) * Math.pow(radix, i * -1)
}
return res
}

指数部分运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// 指数部分运算
function calculateExponent(int, float, exponentSign, exponent) {
// if (float)
let str = int + 0.1 * float
const radix = 10
const exponentInt = calculateInt(exponent)
if (exponentSign === '+') {
str = str * Math.pow(radix, exponentInt)
} else {
str = str * Math.pow(radix, exponentInt * -1)
}
// console.log('Math.abs(str)', Math.abs(str) - Math.floor(str))
// // console.log('Number.EPSILON', Number.EPSILON)
// if (Math.abs(str) - Math.floor(str)) {
// return str.toFixed(1)
// }
return str
}

单元测试

  • 测试用例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
console.log(converStringToDeciaml('1.0e+10'))
console.log('parseFloat 结果', parseFloat('1.0e+10', 10))
console.log(converStringToDeciaml('-1.0e+10'))
console.log('parseFloat 结果', parseFloat('-1.0e+10', 10))
console.log(converStringToDeciaml('1.012e+10'))
console.log('parseFloat 结果', parseFloat('1.012e+10', 10))
console.log(converStringToDeciaml('-1.012e+10'))
console.log('parseFloat 结果', parseFloat('-1.012e+10', 10))
console.log(converStringToDeciaml('.012e+10'))
console.log('parseFloat 结果', parseFloat('.012e+10', 10))
console.log(converStringToDeciaml('-.012e+10'))
console.log('parseFloat 结果', parseFloat('-.012e+10', 10))
console.log(converStringToDeciaml('0.12e+10'))
console.log('parseFloat 结果', parseFloat('0.12e+10', 10))
console.log(converStringToDeciaml('-0.12e+10'))
console.log('parseFloat 结果', parseFloat('-0.12e+10', 10))
console.log(converStringToDeciaml('1.2e-10'))
console.log('parseFloat 结果', parseFloat('1.2e-10', 10))
console.log(converStringToDeciaml('-1.2e-10'))
console.log('parseFloat 结果', parseFloat('-1.2e-10', 10))
console.log(converStringToDeciaml('-1.22'))
console.log('parseFloat 结果', parseFloat('-1.22', 10))
console.log(converStringToDeciaml('Infinity'))
console.log('parseFloat 结果', parseFloat('Infinity', 10))
console.log(converStringToDeciaml('Infinity2222'))
console.log('parseFloat 结果', parseFloat('Infinity2222', 10))
console.log(converStringToDeciaml('22Infinity2222'))
console.log('parseFloat 结果', parseFloat('22Infinity2222', 10))
  • 测试截图
  • 第一部分测试截图
  • 第二部分测试截图

待解决问题

  • 是的 😭 它的浮点数的舍入,我没有想到好的方法,哭唧唧
  • 代码地址

写在后面

  • 等我有空把博客评论搞一下,欢迎大家来告诉我的遗留问题该怎么解决,以及你们在参考我的实现时,有测试用例过不去的地方,也可以评论告诉我,我优化代码。
  • 大家等不及的话,可以去我的 csdn 评论告诉我,卑微小彭,在线求解
  • 祝大家多多发财