寫在前面
- 簡單記錄一下,Javascript parseInt+parseFloat 內部實現,並實現 convertStringToNumber
實踐準備
- 首先我們需要簡單梳理一下我們的實現過程,再根據 ECMAScript-262 標準完善實現
- input: @params: { str } 輸入需要轉換的字符串, { radix } 轉換的指定基數
- 對 input 的簡單處理 StringNumericLiteral
- 規格化 str
-
- 將 str 中可能出現的 StrWhiteSpace 去掉
-
- 將 str 中可能出現的 StrWhiteSpace 去掉
- 判斷 radix 是否合法,僅支持 Decimal / BinaryInteger / OctalInteger / HexInteger radix
- Decimal: 10
- BinaryInteger: 2
- OctalInteger: 8
- HexInteger: 16
- 簡單算法處理過程
- 十進制數
- 處理 Infinity 情況
- 符號位處理
- 小數點
- 科學計數法
- 二進制數
- 八進制數
- 十六進制數
- 十進制數
- 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 }
轉換成十進制
-
實踐思路
- 判斷正負符號位
- 判斷是否爲 Infinity
- 判斷是否爲 整數
- 整數
- 是否爲科學計數法表示
- 是,通過指數位置分成兩部分:整數部分+指數部分
- 否,只需處理整數部分
- 是否爲科學計數法表示
- 小數
- 是否爲科學計數法表示
- 是,通過分割成小數點到指數,分成三部分:整數部分+小數部分+指數部分
- 否,通過小數點位置,分成兩部分:整數部分+小數部分
- 是否爲科學計數法表示
- 整數
-
代碼大致爲:
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))
- 測試截圖
待解決問題
- 是的 😭 它的浮點數的舍入,我沒有想到好的方法,哭唧唧
- 代碼地址
寫在後面
- 歡迎大家來告訴我的遺留問題該怎麼解決,以及你們在參考我的實現時,有測試用例過不去的地方,也可以評論告訴我,我優化代碼。
- 卑微小彭,在線求解
- 祝大家多多發財