JavaSrcipt的数字(number):深入理解内部机制

一、数字的语法

JavaScript中的数字字面量一般用十进制表示。在JavaScript中表示数字的数据类型只有一种Number,这种天使与魔鬼同体的数据类型也就只有js了。

//同时表达整数和浮点数
var a = 78,
    b = 78.3;
console.log(typeof a);//number
console.log(typeof b);//number

然后还有一些奇葩的数字表示法:

var a = 0.42,
    b = .42;
if(a === b){
    console.log("你讲得对。")
}

一般情况下,奇葩都是成双成对的:

var a = 69.0,
    b = 69.;
if(a === b){
    console.log("你讲得对。")
}

这种奇葩你认识它就好了,最好不要出现在你的代码里,记得这种长得丑的写法不报错,但也没事别给自己挖坑。

var a = 89.900,
    b = 98.0;
console.log(a);//89.9
console.log(b);//98

小数点后面最后的面的零不管几个都省略,有时候发点神经,不拖泥带水还是挺潇洒的。然后还有指数计数格式计数与指数显示。

var a = 9E10;
console.log(a);//90000000000--通常我们使用指数计数法但是不会按照指数计数法显示
console.log(a.toExponential());//9e+10--通过toExponential方法转换成指数计数法字符串形式显示
var b = a * a;
console.log(b);//8.1e+21--当数字长度超出一定范围就会自动转成指数计数法
var c = 1 / b;
console.log(c);//1.234567901234568e-22--小写范围超出也会被转成指数计数法

这时候就有一个尴尬的问题来了,显示的是指数计数字面量,有多少人认识呢?所以就需要做数字化字面量处理:(下面的解决方案只能解决JavaScript整数安全范围内和最小小数范围内的数值。超出这个范围的数值听说可以解除Math.js,我也是在别人的博客里看到有介绍过的,没有亲测过。)

function toNonExponential(num) {
    var m = num.toExponential().match(/\d(?:\.(\d*))?e([+-]\d+)/);
    //通过match检索每个整个分组的值(非抓捕除外),并以数组表示
    //(m[1] || '').length;//--\d*--获取到小数位的长度
    //m[2];//--([+-]\d+)--获取到指数
    //(m[1] || '').length - m[2];小数位长度与指数的查(小数的长度)
    //小数位长度为负数时,设置为0--math.max取最大值
    return num.toFixed(Math.max(0, (m[1] || '').length - m[2]));
}
toNonExponential(3.3e-7)     // "0.00000033"
toNonExponential(3e-7)       // "0.0000003"
toNonExponential(1.401e10)   // "14010000000"
toNonExponential(0.0004)     // "0.0004"
View Code

指定小数部分的显示数位:Number.toFixed(x)

var a = 878.59;
a.toFixed(0);//"879"
a.toFixed(1);//"878.6"
a.toFixed(2);//"878.88"
a.toFixed(3);//"878.880"

关于toFixed方法会对数值做四舍五入处理,小数位不够时后面自动用0补齐,返回的值是字符串类型,参数x的取值范围是0~20。接下来看看有效数位处理方法toPrecision(x)

var a = 34.29;
a.toPrecision(1);//"3e+1"
a.toPrecision(2);//"34"
a.toPrecision(3);//"34.3"
a.toPrecision(4);//"34.29"
a.toPrecision(5);//"34.290"
//注意报错
34.toPrecision(3)//SyntaxError
34.toFixed(2)//SytaxError
(34).toPrecision(3);//34.0
(34).toFixed(2);//34.00

Number.toPrecision(x)方法也是会进行四舍五入操作,并且在数位无法表达数值的时候回进行科学计数法处理。值得注意的是toFixed(x)和toPrecision(x)这两个方法在遇到整数字面量调用时,需要用小括号将数值包裹起来,不然会报错。

最后就是一些特殊格式的数字字面量:

console.log(0xf3);//243--数字字面量十六进制
console.log(0363);//243--数字字面量八进制
//ES6支持的新格式
console.log(0o363);//243--ES6版本的数字字面量八进制
console.log(0b11110011);//234--ES6版本数字字面量二进制

二、很小的数字与JavaScript的数值精确问题

console.log(0.1 + 0.2 === 0.3);//false

从上面的代码我们可以看到一个违背了数学逻辑的问题,这是为什么呢?因为JavaScript所遵循IEEE754编程语言规范,这不只是JS的问题,而是所有遵循这一规范的编程语言都如此,也是通常说的二进制浮点数的精度缺陷,在这一精度下0.1+0.2的结果是0.30000000000000004,所以条件判断结果为false。

解决这一问题的办法就是设置一个误差范围,也就是通常说的“机器精度”,这个值通常是2^-52(2.220446049250313e-16)。在ES6中该值定义在Number.EPSILON中,在ES6的环境下我们可以直接拿来用,来版本环境可以通过计算获得这个值来使用:

if(!Number.EPSILON){
    Number.EPSILON = Math.pow(2,-52);
}
//这时候我们就可以来解决两个数字是否相等的问题了
function numbersCLoseEE(n1,n2){
    return Math.abs(n1 - n2) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCLoseEE(a,b);

三、整数(范围、检测、32位有符号整数)

3.1JavaScript中整数最大可呈现2^53 - 1,即9007199254740991,在ES6中被定义为Number.MAX_SAFE_INTEGER。整数最小可呈现-(2^53 - 1),即-9007199254740991,在ES6中定义为Number.MIN_SAFE_INTEGER。超出这个范围的数字就只能采用字符串的方式来呈现了。

3.2关于检测一个数值是否是整数,可以使用ES6中的NumBer.isInteger(..)方法,老版本就只能自己做兼容处理:

Number.isInteger(42);        //true
Number.isInteger(42.00);    //true
Number.isInteger(42.3);        //false
//Number.isInteger(..)的兼容模式
if(!Number.isInteger){
    Number.isInteger = function(num){
        return typeof num == "number" && num % 1 == 0;
    }
}

关于检测一个数值是否是安全整数(即在最大范围和最小范围内的整数),可以使用ES6中的Number.isSafeInteger(...)方法,老版本做兼容处理:

if(!Number.isSafeInteger){
    Number.isSafeInteger = function(num){
        return Number.isInteger(num) && Math.abs(num) <= Number.MAX_SAFE_INTEGER;
    }
}

ES 6 增加了以下三个 Number 对象的属性:

  • EPSILON: 表示 1 和比最接近 1 且大于 1 的最小 Number 之间的差别
  • MIN_SAFE_INTEGER: 表示在 JavaScript中最小的安全的 integer 型数字 (-(253 - 1))。
  • MAX_SAFE_INTEGER: 表示在 JavaScript 中最大的安全整数(253 - 1)。

3.3JavaScript32位有符号整数:

虽然最大整数能够达到53位,但是有些数字操作只适应于32位数字,所以这些操作的数字范围只能在Math.pow(-2,31)到Math.pow(2,31)。

或运算符(“|”)只适应于32位以内的整数运算,所以有任意大小整数通过或运算符运算的话没有意义,比如任意大小整数a与0的运算,(a | 0)从运算范围来理解,这个运算本质上不存在任何意义。

四、特殊数字

 1.不是数字的数字(NaN)

 数学运算时,操作数不是数字类型或者无法解析为常规的十进制或十六进制数字,就无法返回有效的数字,这种情况就会返回NaN。

var a = 2 / "foo";        //NaN
typeof a === "number";    //true

作为一个不是数字的Number类型的值,有一个特性是它不等于任何值,包括他自己(这家伙傻的可爱)。

var a = 2 / "foo";
a == NaN;     //false
a === NaN;     //false
//而且它很确定自己就不是自己
NaN != NaN; // true

既然无法进行比较,那如果在程序中我们由不得不比较它呢?全局对象window上有一个方法isNaN(...)可以用来判断它!

var a = 2 / "fuh";
window.isNaN(a);//true

但是,全局对象上的isNaN(...)并不靠谱,这个方法在检测值的时候,只要被判断的值不是数字就返回true。

var a = "aaa";
console.log(window.isNaN(a));//true

然后ES6在Number对象上添加了isNaN的方法解决了这个问题,但是老版本的浏览器就得要做兼容处理了:

if(!Number.isNaN){
    Number.isNaN = function(n){
        return (
            typeof n === "number" && 
            window.isNaN(n)
        );
    };
}
var a = 2 / "abc";
var b = "abc";
console.log(Number.isNaN(a));//true
console.log(Number.isNaN(b));//false

这个兼容的写法有点累赘,其实还有一个更简单的写法:

if(!Number.isNaN){
    Number.isNaN = function(n){
        return n !== n;
    }
}

2.无穷数

在JavaScript中除数是可以为0的,当1除以0时结果为Infinity(即Number.POSITIVE_INFINITY)。无穷数在JavaScript中也存在正无穷和负无穷,当一个负数除以0时,就可以得到-Infinity。

var a = 1 / 0;//Infinity
var a = -1 / 0;//-Infinity

因为JavaScript遵循IEEE754规范,在断定断定无穷数时存在向上和向下取值行为,我们知道在JavaScript中定义了最大值属性(Number.MAX_VALUE);但这个最大值并不代表再加上一个数就是无穷了,而且这个数跟一的差距还特别大,这种界定无穷的方法通俗来讲就是这个介于最大值和无穷之间的数更接近那一边。

console.log(Number.MAX_VALUE);//1.7976931348623157e+308
var a = Number.MAX_VALUE;
a + a;                //    Infinity
a + Math.pow(2,970);//    Infinity
a + Math.pow(2,969);//    1.7976931348623157e+308
//这部分不太必要过多研究,可能在你的工作中永远不会用到这个知识点,有兴趣的话作为学术讨论就好

要注意一点的是无穷除以无穷的结果为NaN。

3.零值

在JavaSrcipt中零是存在正负值的特殊数值,当零除以负数时得到的就是一个(-0),当然一个负数乘以零也是得到(-0);

var a = 0 / -5;//-0
var b = 0 * -6;//-0

在控制台打印时,这个结果并不一定准确,会因不同的浏览器和版本有不同的情况;还有一个情况就是在零值转换为字符串类型是就不会保留负值这个特性了,但是当带有负号的数字0的字符串转换成数字格式时又会保留负值。

var a = 0 / -9;    //-0
a.toString();      //"0"
a + "";            //"0"
String(a);         //"0"

+ "-0";            //-0
Number("-0");      //-0
JSON.parse("-0");  //-0

既然零值存在正负特性,但是在普通的逻辑运算看来0和-0是同一个东西,这两个东西是相等的。那么就会需要有判断正负的机制,写一个判断值为负零(-0)的方法:

function isNegZero(n){
    n = Number(n);
    return (n === 0) && (1 / n === -Infinity);
}

 

posted @ 2019-01-10 19:32  他乡踏雪  阅读(826)  评论(0编辑  收藏  举报