convertStringToNumber

寫在前面

  • 簡單記錄一下,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

詳細實踐

  • 根據上面的實踐準備
/*
* @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 進行匹配

    /*
    * @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 根據指定基數進行轉換

    /*
    * @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)
    
    • 單元測試
      • 單元測試
  • 根據指定基數進行相應算法轉換

    • 二進制:

      
      // 將 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)
      
      • 單元測試
        • 單元測試截圖
    • 八進制

      • 同上

        // 將 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

          // 將 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))
        
      • 單元測試

        • 單元測試截圖
  • 最終二/八/十六進制根據指定基數進行相應算法轉換方法爲:

    	/*
      * @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. 否,通過小數點位置,分成兩部分:整數部分+小數部分
  • 代碼大致爲:

  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
  }

判斷正負,並根據符號位格式化字符串


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

根據符號位格式化字符串(移除字符號位)

  // 根據符號位格式化字符串
  function formatStrBySign(str) {
    // 如果 str 的首字符爲‘+’或'-',則移除首字符
    if (str.indexOf('-') == 0 || str.indexOf('+') == 0) {
      str = str.substring(1, str.length)
    }
    return str
  }

判斷是否爲Infinity

  // 是否爲 Infinity
  function isInfinity(str) {
    const testReg = /^Infinity/
    let resReg = testReg.exec(str)
    return resReg && resReg[0]
  }

切割字符串,分成整數+小數+指數位符號+指數部分

  // 切割字符串
  /*
  * @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
  }

整數部分運算


  // 整數部分運算
  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
  }

小數部分運算


  // 小數部分運算
  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
  }

指數部分運算


  // 指數部分運算
  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
  }

單元測試

  • 測試用例
  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))
  • 測試截圖
  • 第一部分測試截圖
  • 第二部分測試截圖

待解決問題

  • 是的 😭 它的浮點數的舍入,我沒有想到好的方法,哭唧唧
  • 代碼地址

寫在後面

  • 歡迎大家來告訴我的遺留問題該怎麼解決,以及你們在參考我的實現時,有測試用例過不去的地方,也可以評論告訴我,我優化代碼。
  • 卑微小彭,在線求解
  • 祝大家多多發財
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章