解決原生JS不能處理超高精度的小數運算,手動分割小數來進行四則運算。

JavaScript(JS)在處理大多數數學運算時表現出色。但由於它採用了 IEEE 754 雙精度浮點數標準,導致在處理超高精度小數時,常會遇到精度丟失的問題。例如,直接用JS 算 0.1 + 0.2 會得到令人抓狂的結果:0.30000000000000004以及無法算到小術後第17位以上的運算。這樣的誤差在金融計算或科學計算等需要高精度的會造成嚴重影響。

為了解決這問題,除了使用專門的套件,如 BigNumber.jsdecimal.js 等,還有一種手動分割小數來進行四則運算的方法。此方法的是將小數拆解為整數進行運算,然後再手動處理結果的小數部分。以下是用原生JS寫的加減乘除,專門用來處理以上獎的情況。

//加法
function add(a, b) {
    let [intA, decA] = a.split('.');
    let [intB, decB] = b.split('.');
    
    decA = decA || '';
    decB = decB || '';
    
    const maxDecLength = Math.max(decA.length, decB.length);
    decA = decA.padEnd(maxDecLength, '0');
    decB = decB.padEnd(maxDecLength, '0');
    
    let intSum = BigInt(intA) + BigInt(intB);
    let decSum = BigInt(decA) + BigInt(decB);
    
    if (decSum.toString().length > maxDecLength) {
        intSum += BigInt(1);
        decSum = decSum.toString().slice(1);
    }
    
    return intSum.toString() + '.' + decSum.toString().padStart(maxDecLength, '0');
}

//減法
function subtract(a, b) {
    let [intA, decA] = a.split('.');
    let [intB, decB] = b.split('.');
    
    decA = decA || '';
    decB = decB || '';
    
    const maxDecLength = Math.max(decA.length, decB.length);
    decA = decA.padEnd(maxDecLength, '0');
    decB = decB.padEnd(maxDecLength, '0');
    
    let intDiff = BigInt(intA) - BigInt(intB);
    let decDiff = BigInt(decA) - BigInt(decB);
    
    if (decDiff < 0) {
        intDiff -= BigInt(1);
        decDiff = BigInt('1' + '0'.repeat(maxDecLength)) + decDiff;
    }
    
    return intDiff.toString() + '.' + decDiff.toString().padStart(maxDecLength, '0');
}

//乘法
function multiply(a, b) {
    let [intA, decA] = a.split('.');
    let [intB, decB] = b.split('.');
    
    decA = decA || '';
    decB = decB || '';
    
    const totalDecLength = decA.length + decB.length;
    
    const numA = BigInt(intA + decA);
    const numB = BigInt(intB + decB);
    
    let product = numA * numB;
    
    let productStr = product.toString();
    if (totalDecLength > 0) {
        productStr = productStr.padStart(totalDecLength + 1, '0');
        const intPart = productStr.slice(0, -totalDecLength);
        const decPart = productStr.slice(-totalDecLength);
        return intPart + '.' + decPart;
    } else {
        return productStr;
    }
}

//除法(需要手動調整要算到第幾位precision)-預設算到25位
function divide(a, b, precision = 25) {
    a = typeof a === 'number' ? a.toString() : a;
    b = typeof b === 'number' ? b.toString() : b;

    let [intA, decA = ''] = a.split('.');
    let [intB, decB = ''] = b.split('.');
    
    const totalDecLengthA = decA.length;
    const totalDecLengthB = decB.length;
    
    const numA = BigInt(intA + decA.padEnd(totalDecLengthA, '0'));
    const numB = BigInt(intB + decB.padEnd(totalDecLengthB, '0'));
    
    const scaleFactor = BigInt(10 ** (precision + totalDecLengthB - totalDecLengthA));
    
    let quotient = (numA * scaleFactor) / numB;
    
    let quotientStr = quotient.toString().padStart(precision + 1, '0');
    
    const intPart = quotientStr.slice(0, -precision);
    const decPart = quotientStr.slice(-precision);

    // 如果小數部分全為 0 或者小數點後面不需要保留,則去除小數點部分
    if (decPart.replace(/0+$/, '').length === 0) {
        return intPart;  // 整數結果
    } else {
        // 保留精度範圍內的小數位數,去除多餘的 0
        let result = intPart + '.' + decPart;
        result = result.replace(/\.?0+$/, '');  // 移除結尾的多餘零和小數點
        return result;
    }
}


let a = "1000.99999999999999999999";
let b = "0.00000000000000000001";

let c = "4";
let d = "2";

let resultAdd = add(a, b);
let resultSubtract = subtract(a, b);
let resultMultiply = multiply(a, b);
let resultDivide = divide(a, b, 25);
let resultDivide2 = divide(c, d, 1);

console.log("加法結果:", resultAdd);
console.log("減法結果:", resultSubtract);
console.log("乘法結果:", resultMultiply);
console.log("除法結果:", resultDivide);
console.log("除法結果2:", resultDivide2);

輸出結果

+ There are no comments

Add yours

發表迴響