Loading ...

JavaScript策略设计时数值计算精度问题解决方案

Author: 小小梦, Created: 2020-03-19 18:02:59, Updated: 2020-03-20 09:55:35

JavaScript策略设计时数值计算精度问题解决方案

在编写JavaScript策略时,由于脚本语言自身的一些问题,经常导致计算时数值精确度问题。对于程序中一些计算、逻辑判断有一定影响。例如,在计算1 - 0.8或者0.33 * 1.1时,会计算出有误差的数据:

function main() {
    var a = 1 - 0.8
    Log(a)
   
    var c = 0.33 * 1.1
    Log(c) 
}

img

那么如何解决此类问题呢?问题根源在于:

浮点数值的最高进度是17位小数,但在进行运算的时候其精确度却远远不如整数;整数在进行运算的时候都会转成10进制; 而Java和JavaScript中计算小数运算时,都会先将十进制的小数换算到对应的二进制,一部分小数并不能完整的换算为二进制,这里就出现了第一次的误差。待小数都换算为二进制后,再进行二进制间的运算,得到二进制结果。然后再将二进制结果换算为十进制,这里通常会出现第二次的误差。

为了解决这个问题,在网上搜索了一些解决方案,经过测试使用,如下方案比较好的解决了这个问题:

function mathService() {
    // 加法
    this.add = function(a, b) {
        var c, d, e;
        try {
            c = a.toString().split(".")[1].length;   // 获取a的小数位长度
        } catch (f) {
            c = 0;
        }
        try {
            d = b.toString().split(".")[1].length;   // 获取b的小数位长度
        } catch (f) {
            d = 0;
        }
        //先求e,把a、b 同时乘以e转换成整数相加,再除以e还原
        return e = Math.pow(10, Math.max(c, d)), (this.mul(a, e) + this.mul(b, e)) / e;
    }
    
    // 乘法
    this.mul = function(a, b) {       
        var c = 0,
            d = a.toString(),         // 转换为字符串 
            e = b.toString();         // ...
        try {
            c += d.split(".")[1].length;      // c 累加a的小数位长度
        } catch (f) {}
        try {
            c += e.split(".")[1].length;      // c 累加b的小数位长度
        } catch (f) {}
        // 转换为整数相乘,再除以10^c ,移动小数点,还原,利用整数相乘不会丢失精度
        return Number(d.replace(".", "")) * Number(e.replace(".", "")) / Math.pow(10, c);
    }

    // 减法
    this.sub = function(a, b) {
        var c, d, e;
        try {
            c = a.toString().split(".")[1].length;  // 获取a的小数位长度
        } catch (f) {
            c = 0;
        }
        try {
            d = b.toString().split(".")[1].length;  // 获取b的小数位长度
        } catch (f) {
            d = 0;
        }
        // 和加法同理
        return e = Math.pow(10, Math.max(c, d)), (this.mul(a, e) - this.mul(b, e)) / e;
    }

    // 除法
    this.div = function(a, b) {
        var c, d, e = 0,
            f = 0;
        try {
            e = a.toString().split(".")[1].length;
        } catch (g) {}
        try {
            f = b.toString().split(".")[1].length;
        } catch (g) {}
        // 同理,转换为整数,运算后,还原
        return c = Number(a.toString().replace(".", "")), d = Number(b.toString().replace(".", "")), this.mul(c / d, Math.pow(10, f - e));
    }
}

function main() {
    var obj = new mathService()
    
    var a = 1 - 0.8
    Log(a)
    
    var b = obj.sub(1, 0.8)
    Log(b)
    
    var c = 0.33 * 1.1
    Log(c)
    
    var d = obj.mul(0.33, 1.1)
    Log(d)
}

img

究其原理,是把要做运算的两个操作数转换为整数去计算避免精度问题,在计算后(根据转换为整数时的放大倍数)对计算结果运算还原,得出准确结果。

这样当我们想让程序在盘口价格加一跳最小价格精度的时候挂单,就不用担心数值精度问题了

function mathService() {
....    // 省略
}

function main() {
    var obj = new mathService()
    var depth = exchange.GetDepth()
    exchange.Sell(obj.add(depth.Bids[0].Price, 0.0001), depth.Bids[0].Amount, "买一订单:", depth.Bids[0]) 
}

有兴趣的同学,可以读一下代码,了解计算过程,欢迎提出问题,一起学习一起进步。


Related

More