月度存档: 十二月 2017

关于JS小数运算不精确的探讨

各位老司机应该知道,很多时候做小数运算的时候会出现小数溢出。常见的场景是做价格运算,测试经常报bug。

于是针对这个奇特的问题,开始了寻根之旅。

举个例子:0.2+0.1=0.30000000000000004

为什么会这样?

其实很多其他计算机语言都有这个问题。计算机中的所有运算都会经过二进制转换再计算的。

0.2的二进制是  

0.2 * 2 = 0.4 取整数位  0  得 0.0

0.4 * 2 = 0.8 取整数位  0  得 0.00

0.8 * 2 = 1.6 取整数位  1  得 0.001

0.6 * 2 = 1.2 取整数位  1  得 0.0011

0.2 * 2 = 0.4 取整数位  0  得 0.00110

(n)….

太长了 于是直接在控制台上打印 

var num = 0.2;

num.toString(2)

// "0.001100110011001100110011001100110011001100110011001101"

同理算出 0.1的二进制为

// "0.0001100110011001100110011001100110011001100110011001101"

两个二进制相加得出    

// "0.0100110011001100110011001100110011001100110011001100111"

根据这个IEEE754的标准 就会把最后的 1 舍去变成

0.0100110011001100110011001100110011001100110011001101

然后转成十进制得出结果是 0.30000000000000004

再举个例子 0.1+0.3 = 0.4 

为什么这两个数不会产生溢出呢?

算出 0.1的二进制为

// "0.0001100110011001100110011001100110011001100110011001101"

算出 0.4的二进制为

// "0.01100110011001100110011001100110011001100110011001101"

两个二进制相加得出    

// "0.1000000000000000000000000000000000000000000000000000001"

根据这个IEEE754的标准 就会把最后的第52位进一舍零,得到

"0.1000000000000000000000000000000000000000000000000000"

剩下0.1 转换十进制为0.5

找了网上的一张图,其实还不是完全看懂指数位是什么。。。。

QQ图片20171226234411.png

怎么处理?

/**

 * 加法运算,避免数据相加小数点后产生多位数和计算精度损失。

 * 

 * @param a加数1 | b加数2

 */

function Add(a, b) {

    var n, n1, n2;

    try {

        n1 = a.toString().split(".")[1].length;

    } catch (e) {

        n1 = 0;

    }

    try {

        n2 = b.toString().split(".")[1].length;

    } catch (e) {

        n2 = 0;

    }

    n = Math.pow(10, Math.max(n1, n2));

    return (a * n + b * n) / n;

}

/**

 * 减法运算,避免数据相减小数点后产生多位数和计算精度损失。

 * 

 * @param a被减数  |  b减数

 */

function Sub(a, b) {

    var n, n1, n2;

    var precision; // 精度

    try {

        n1 = a.toString().split(".")[1].length;

    } catch (e) {

        n1 = 0;

    }

    try {

        n2 = b.toString().split(".")[1].length;

    } catch (e) {

        n2 = 0;

    }

    baseNum = Math.pow(10, Math.max(n1, n2));

    n = Math.pow(10, Math.max(n1, n2));

    return (a * n – b * n) / n;

}

/**

 * 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。

 * 

 * @param a被乘数 | b乘数

 */

function Mul(a, b) {

    var n1 = 0,

        n2 = 0;

    try {

        n1 += a.toString().split(".")[1].length;

    } catch (e) {}

    try {

        n2 += b.toString().split(".")[1].length;

    } catch (e) {}

    return (a * Math.pow(10, n1)) * (b * Math.pow(10, n2)) / Math.pow(10, n1 + n2);

}

/**

 * 除法运算,避免数据相除小数点后产生多位数和计算精度损失。

 * 

 * @param a被除数 | b除数

 */

function Div(a, b) {

    var n1 = 0,

        n2 = 0;

    var baseNum3, baseNum4;

    try {

        n1 += a.toString().split(".")[1].length;

    } catch (e) {}

    try {

        n2 += b.toString().split(".")[1].length;

    } catch (e) {}

    return (a * Math.pow(10, n1)) / (b * Math.pow(10, n2)) / Math.pow(10, n1 – n2);

}

// js小数相加,精确的结果应当是10090.701

var numAdd = Add(9856.556, 234.145);

// js小数相减,精确的结果应当是422.441

var numSub = Sub(656.55, 234.109);

// js小数相乘,精确的结果应当是51.285

var numMul = Mul(Mul(34.19, 0.03), 50);

// js小数相除,精确的结果应当是40.0936

var numDiv = Div(100.234, 2.5);

思想是计算出有几位小数(n),先转化成整数,保证用整数进行计算,再除以 10*n; 

从React diff算法原理中得到的React编写优化方案

    讲起React,当然不能缺少它最重要的特性虚拟Dom,但是很多人都讲不清楚虚拟Dom的内在原理,其实关键点还是这Diff算法。

    

    Diff算法说简单的说就是遍历二叉树,render前和render后的每一个节点做匹配。这里说的节点就是DOM节点但是不是真的DOM节点,而是一个对象类似于

    {

        tagname:div

        className:"a",

        style:"width:100px",

        onClick:function(){}

        children:[

            {

                    tagname:span,

                    className:"b",

                    text:"hello",

                    key:1

            },{

                    tagname:span,

                    className:"c",

                    text:"world",

                    key:2

            }

        ]

    }

    对应的DOM如下:

    <div class="a" style="width:100px" onclick="">

        <span class="b"></span>

        <span class="c"></span>

    </div>

    根据二叉树的遍历,从上到下,从左到右遍历,一边遍历,一遍跟旧节点对比。

    1.如果一些基本属性如class,style,text变化了(注意不包括id,key,children),则只修改对应DOM的 class,style,innerText

    2.如果tagname变化了,那就整个元素删掉,重新creatElement('xx')新的标签。这时候原来的childrem都不会再遍历。

    3.如果对象中有key,而且相对于同级元素唯一,那么对于children数组操作的时候,会按key找到元素,修改对应的值。这样的优势是,如果在children中插入一个新的兄弟元素,比如插入头部,它就会判断children[0]的key,跟新插入的是否一致,如果不一致,则创建新元素,然后匹配下一个兄弟元素,并检索是否在旧的childrem是否存在同样id的节点,如果存在,而且被修改过值,则像上面的 1 一样操作。加key优势是不必全量销毁和创建同级DOM。盗一张图类似这样 

WechatIMG101.jpeg

  1. 加key,只对同级别元素有效

  2. 不要变标签,最好能加css隐藏掉

    {if isOk == true?<div>1</div>:<span>1</span>}像这样的写法需要经过销毁和创建DOM的过程,如果换成

    <div style={{display:isOK?block:none}}>1</div>:<span style={{display:isOK?inline:none}}>1</span>,这样就不会频繁销毁和创建

  3. boolean shouldComponentUpdate(object nextProps, object nextState)

    如果很有把握这次render可以不进行,就可以用这个方法对比props和state,return false;阻止render进行.