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) 
}

那麼如何解決此類問題呢?問題根源在於:

浮點數值的最高進度是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)
}

究其原理,是把要做運算的兩個操作數轉換爲整數去計算避免精度問題,在計算後(根據轉換爲整數時的放大倍數)對計算結果運算還原,得出準確結果。

這樣當我們想讓程序在盤口價格加一跳最小价格精度的時候掛單,就不用擔心數值精度問題了

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]) 
}

有興趣的同學,可以讀一下代碼,瞭解計算過程,歡迎提出問題,一起學習一起進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章