Skip to content

Instantly share code, notes, and snippets.

@Gaubee
Last active March 15, 2016 15:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Gaubee/925cae93acc1c9b9fe59 to your computer and use it in GitHub Desktop.
Save Gaubee/925cae93acc1c9b9fe59 to your computer and use it in GitHub Desktop.
Safe ParseInt. 安全的ParseInt函数,不使用JS中已经内置的任何函数来实现
const parseInt = require("./parseInt");
const assert = require("assert");
const assertEquals = assert.deepStrictEqual;
const assertTrue = assert.ok;
/*
* https://github.com/v8/v8/blob/01590d660d6c8602b616a82816c4aea2a251be63/test/mjsunit/parse-int-float.js
*/
assertEquals(0, parseInt('0'));
assertEquals(0, parseInt(' 0'));
assertEquals(0, parseInt(' 0 '));
assertEquals(77, parseInt('077'));
assertEquals(77, parseInt(' 077'));
assertEquals(77, parseInt(' 077 '));
assertEquals(-77, parseInt(' -077'));
assertEquals(3, parseInt('11', 2));
assertEquals(4, parseInt('11', 3));
assertEquals(4, parseInt('11', 3.8));
assertEquals(0x12, parseInt('0x12'));
assertEquals(0x12, parseInt('0x12', 16));
assertEquals(0x12, parseInt('0x12', 16.1));
assertEquals(0x12, parseInt('0x12', NaN));
assertTrue(isNaN(parseInt('0x ')));
assertTrue(isNaN(parseInt('0x')));
assertTrue(isNaN(parseInt('0x ', 16)));
assertTrue(isNaN(parseInt('0x', 16)));
assertEquals(12, parseInt('12aaa'));
assertEquals(0.1, parseFloat('0.1'));
assertEquals(0.1, parseFloat('0.1aaa'));
assertEquals(0, parseFloat('0aaa'));
assertEquals(0, parseFloat('0x12'));
assertEquals(77, parseFloat('077'));
assertEquals(Infinity, parseInt('1000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '0000000000000'));
assertEquals(Infinity, parseInt('0x10000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '000000000000000000000000000000000000000000000000000000000000000000000000'
+ '0000000000000'));
var i;
var y = 10;
for (i = 1; i < 21; i++) {
var x = eval("1.2e" + i);
assertEquals(Math.floor(x), parseInt(x));
x = eval("1e" + i);
assertEquals(x, y);
y *= 10;
assertEquals(Math.floor(x), parseInt(x));
x = eval("-1e" + i);
assertEquals(Math.ceil(x), parseInt(x));
x = eval("-1.2e" + i);
assertEquals(Math.ceil(x), parseInt(x));
}
for (i = 21; i < 53; i++) {
var x = eval("1e" + i);
assertEquals(1, parseInt(x));
x = eval("-1e" + i);
assertEquals(-1, parseInt(x));
}
assertTrue(isNaN(parseInt(0/0)));
assertTrue(isNaN(parseInt(1/0)), "parseInt Infinity");
assertTrue(isNaN(parseInt(-1/0)), "parseInt -Infinity");
assertTrue(isNaN(parseFloat(0/0)));
assertEquals(Infinity, parseFloat(1/0), "parseFloat Infinity");
assertEquals(-Infinity, parseFloat(-1/0), "parseFloat -Infinity");
var state;
var throwingRadix = { valueOf: function() { state = "throwingRadix"; throw null; } };
var throwingString = { toString: function() { state = "throwingString"; throw null; } };
state = null;
try { parseInt('123', throwingRadix); } catch (e) {}
assertEquals(state, "throwingRadix");
state = null;
try { parseInt(throwingString, 10); } catch (e) {}
assertEquals(state, "throwingString");
state = null;
try { parseInt(throwingString, throwingRadix); } catch (e) {}
assertEquals(state, "throwingString");
// And finally, check that the Harmony additions to the Number
// constructor is available:
// assertTrue("parseInt" in Number);
// assertTrue("parseFloat" in Number);
// assertSame( Number.parseInt, parseInt);
// assertSame(Number.parseFloat, parseFloat);
// assertEquals(Number.parseFloat('0.1'), parseFloat('0.1'));
assertEquals(Number.parseInt('0xea'), parseInt('0xEA'));
// var _test_num = 100000;
// const Number_parseInt = Number.parseInt;
// console.time("parseInt")
// for (var _i = 0; _i < _test_num; _i += 1) {
// parseInt('0xEA')
// }
// console.timeEnd("parseInt")
// console.time("Number.parseInt")
// for (var _i = 0; _i < _test_num; _i += 1) {
// Number_parseInt('0xEA')
// }
// console.timeEnd("Number.parseInt")
/*
* 实现原则:确保安全性与可用性,不使用任何已存在的函数类方法,仅仅使用不可被重写的JS运算符
* 即便是注入者重写了valueOf和toString,也能确保运行
*/
String.prototype.valueOf = function() {
return this + "1"
};
String.prototype.toString = function() {
return this + "2"
};
Number.prototype.valueOf = function() {
return this + 1
};
Number.prototype.toString = function() {
return this + 2
};
Object.prototype.valueOf = function() {
return this + 1
};
Object.prototype.toString = function() {
return this + 2
};
/*
* 数组下标对应的36进制的字符
* 不使用split(''),确保安全性
*/
var num_table = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'z', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
var lower_table = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'z', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
var upper_table = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'Z', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
var upper_len = upper_table.length;
var upper_map = (function() {
var res = {};
for (var i = 0; i < upper_len; i += 1) {
res[upper_table[i]] = i;
}
return res;
}());
function get_char_lower(_char) {
var _char_num = upper_map[_char];
if (0 <= _char_num && _char_num < upper_len && upper_table[_char_num] === _char) {
return lower_table[_char_num];
}
return _char;
};
function upper_to_lower(str) {
var res = ""
for (var i = 0, len = str.length; i < len; i += 1) {
res += get_char_lower(str[i]);
}
return res;
};
/*
* trim-left
*/
var whitespace_table = [' ', '\n', '\r', '\t', '\f', '\x0b', '\xa0', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005', '\u2006', '\u2007', '\u2008', '\u2009', '\u200a', '\u200b', '\u2028', '\u2029', '\u3000'];
var whitespace_len = whitespace_table.length;
var whitespace_map = (function() {
var res = {};
for (var i = 0; i < whitespace_len; i += 1) {
res[whitespace_table[i]] = i;
}
return res;
}());
function whitespace_has(_char) {
var _char_num = whitespace_map[_char];
if (0 <= _char_num && _char_num < whitespace_len && whitespace_table[_char_num] === _char) {
return true;
}
};
function left_trim_index(str) {
for (var i = 0, len = str.length; i < len; i += 1) {
if (!whitespace_has(str[i])) {
return i;
}
}
};
function toInt(value, _radix) {
/*
* 强制转换成字符串来处理
* PS: 不使用 value = "" + value ,否则一旦改变了数据类型,会导致代码速度变慢
*/
var value_str = upper_to_lower("" + value);
var radix = ~~_radix; // 0,no number == 0
if (radix < 0 || radix > 36) { // NaN, 0~36
return nan;
}
var nan = +"a"; // NaN
var value_str_len = value_str.length;
var start_index = left_trim_index(value_str);
var res = 0;
var base = 1;
if (value_str[start_index] === "-") {
base = -1;
start_index += 1;
}
/*
* 取得正确的进制数
* NaN: 0x*(16) , *(10)
* 16:0x*
*/
if ((!radix || radix === 16) && value_str[start_index] === "0" && value_str[start_index + 1] === "x") {
radix = 16;
start_index += 2; //忽略0x
} else if (!radix) {
radix = 10
}
/*
* 每一个字符对应的数字,临时生成,使用缓存的话可能会被通过Object.prototype来注入缓存,导致安全问题
*/
var num_map = {};
for (var i = 0; i < radix; i += 1) {
num_map[num_table[i]] = i;
}
for (var i = start_index, c, c_num; i < value_str_len; i += 1) {
c = value_str[i];
c_num = num_map[c];
/*
* 校验c_num的准确性,避免外部重写了Object.prototype导致安全问题
* 如果单纯使用 c_num < radix 这种简单校验,会导致类似的 num_map['.'] = 5 之类的安全问题发生
* 如果单纯使用 num_table[c_num] === c,会导致 num_map['.'] = 100, num_table[100] === '.' 之类的安全问题
* 所以必须二者搭配来校验
*/
// console.log(c, c_num)
if (0 <= c_num && c_num < radix && num_table[c_num] === c) {
res = res * radix + c_num;
} else {
break
}
}
return i === start_index ? nan : res * base; //如果第一个字符就是非法字符(包括空字符)的话,等于res是空的,返回NaN , 注意:'0x'返回NaN,因为被当成16进制处理
};
module.exports = toInt;
console.log(toInt("0XeA"), "===", 234)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment