math - precisions - single precision floating point binary numbers




浮點數學是否破碎? (18)

硬件設計師的觀點

我相信自從我設計和構建浮點硬件以來,我應該為此添加一個硬件設計師的視角。 了解錯誤的起源可能有助於理解軟件中發生的事情,最終,我希望這有助於解釋為什麼浮點錯誤發生並且似乎隨著時間的推移而積累的原因。

1.概述

從工程角度來看,大多數浮點運算都會有一些錯誤因素,因為執行浮點計算的硬件只需要在最後一個位置的誤差小於一個單位的一半。 因此,對於單個操作而言 ,許多硬件將停止在僅產生小於一個單元的一半的誤差的精度,這在浮點除法中尤其成問題。 單個操作的構成取決於單元占用的操作數。 對於大多數情況,它是兩個,但有些單位需要3個或更多操作數。 因此,不能保證重複操作會導致所需的錯誤,因為錯誤會隨著時間的推移而增加。

2.標準

大多數處理器遵循IEEE-754標準,但有些使用非規範化或不同標準。 例如,在IEEE-754中存在非規範化模式,其允許以精度為代價來表示非常小的浮點數。 然而,以下內容將涵蓋IEEE-754的標準化模式,這是典型的操作模式。

在IEEE-754標準中,只要硬件設計者在最後一個地方不到一個單位的一半,就允許任何錯誤/ epsilon值,並且結果只需要小於最後一個單位的一半。一次操作的地方。 這解釋了為什麼當重複操作時,錯誤加起來。 對於IEEE-754雙精度,這是第54位,因為53位用於表示浮點數的數字部分(標準化),也稱為尾數(例如5.3e5中的5.3)。 接下來的部分將詳細介紹各種浮點運算的硬件錯誤原因。

3.分區舍入錯誤的原因

浮點除法誤差的主要原因是用於計算商的除法算法。 大多數計算機系統使用乘法乘法來計算除法,主要在Z=X/YZ = X * (1/Y) 。 迭代地計算除法,即每個週期計算商的一些比特直到達到期望的精度,對於IEEE-754,在最後的位置具有小於一個單位的誤差。 Y(1 / Y)的倒數表被稱為慢除法中的商選擇表(QST),商選擇表的位大小通常是基數的寬度,或者是位數的比特數。在每次迭代中計算的商,加上一些保護位。 對於IEEE-754標準,雙精度(64位),它將是分頻器的基數的大小,加上一些保護位k,其中k>=2 。 因此,例如,用於一次計算2位商(分數4)的除法器的典型商數選擇表將是2+2= 4位(加上幾個可選位)。

3.1除法舍入誤差:倒數近似

商選擇表中的倒數取決於劃分方法 :諸如SRT劃分的慢劃分,或諸如Goldschmidt劃分的快速劃分; 根據除法算法修改每個條目以試圖產生盡可能低的錯誤。 但無論如何,所有倒數都是實際倒數的近似值 ,並引入了一些誤差因素。 慢速分割和快速分割方法都迭代地計算商,即每一步計算商的一些位數,然後從被除數中減去結果,並且除法器重複這些步驟直到誤差小於一半單位在最後一個地方。 慢速劃分方法在每個步驟中計算商的固定位數,並且通常構建成本較低,並且快速劃分方法計算每步的可變位數並且通常構建成本更高。 除法方法中最重要的部分是它們中的大多數依賴於倒數的近似重複乘法,因此它們容易出錯。

4.其他操作中的捨入錯誤:截斷

所有操作中舍入錯誤的另一個原因是IEEE-754允許的最終答案的截斷模式不同。 有截斷,圓向零, 圓到最近(默認),向下舍入和向上舍入。 對於單個操作,所有方法在最後位置引入小於一個單元的誤差元素。 隨著時間的推移和重複的操作,截斷也會累積地增加結果誤差。 這種截斷誤差在求冪中尤其成問題,它涉及某種形式的重複乘法。

5.重複操作

由於執行浮點計算的硬件僅需要產生一個結果,錯誤小於單個操作的最後一個單位的一半,如果沒有觀察,錯誤將在重複操作上增加。 這就是為什麼在需要有界誤差的計算中,數學家使用諸如 IEEE-754 的最後位置使用舍入到最近的偶數位的方法 ,因為隨著時間的推移,錯誤更可能相互抵消out和Interval Arithmetic結合IEEE 754舍入模式的變化來預測舍入誤差,並糾正它們。 由於與其他舍入模式相比其相對誤差較小,因此舍入到最接近的偶數位(在最後一位)是IEEE-754的默認舍入模式。

請注意,默認舍入模式, 即最後一個位置的捨入到最接近的偶數位 ,可確保一次操作的最後一個位置的誤差小於一個單位的一半。 單獨使用截斷,向上舍入和向下舍入可能會導致錯誤大於最後一個位置的一個單位的一半,但在最後一個位置時小於一個單位,因此不建議使用這些模式,除非它們是用於區間算術。

6.總結

簡而言之,浮點運算中的錯誤的根本原因是硬件中的截斷和在除法的情況下截斷倒數的組合。 由於IEEE-754標准在單個操作中僅需要小於一個單元的一半的誤差,因此除非經過校正,否則重複操作的浮點誤差將相加。

請考慮以下代碼:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

為什麼會出現這些不准確之處?


不,不破,但大多數小數必須近似

摘要

浮點運算精確的,不幸的是,它與我們通常的基數為10的數字表示不匹配,因此事實證明我們經常給它的輸入略微偏離我們寫的。

甚至像0.01,0.02,0.03,0.04 ...... 0.24這樣的簡單數字也不能完全表示為二進制分數。如果你計算0.01,。02,。03 ......,直到達到0.25,你才會得到第2個基數代表的第一個分數。如果你嘗試使用FP,你的0.01會略微偏離,所以將其中25個添加到精確的0.25的唯一方法就是需要一個涉及保護位和舍入的長鏈因果關係。這很難預測,所以我們舉起手來說“FP不精確”,但事實並非如此。

我們不斷給FP硬件提供基本10中看似簡單的東西,但它是基數2中的重複部分。

這怎麼發生的?

當我們用十進制寫時,每個分數(特別是每個終止小數)都是形式的有理數

a /(2 n x 5 m

在二進制中,我們只得到2 n項,即:

a / 2 n

因此,在小數,我們不能代表1 / 3。因為基數10包括2作為素數因子,所以我們可以寫為二進制分數的每個數可以寫為基數10分數。然而,我們寫作基數10分數的任何東西都不能用二進製表示。在0.01,0.02,0.03 ... 0.99的範圍內,我們的FP格式中只能表示三個數字:0.25,0.50和0.75,因為它們是1 / 4,1 / 2和3/4,所有數字只使用2 n項的素因子。

在基座10,我們不能代表1 / 3。但是,在二進制,我們不能做1 / 10 1 / 3

因此,雖然每個二進制分數都可以用十進制編寫,但反之則不然。事實上,大多數小數部分以二進制重複。

處理它

開發人員通常被指示進行<epsilon比較,更好的建議可能是捨入到整數值(在C庫中:round()和roundf(),即保持FP格式)然後進行比較。舍入到特定的小數部分長度解決了輸出的大多數問題。

此外,關於真正的數字運算問題(FP在早期,可怕的昂貴計算機上發明的問題)宇宙的物理常數和所有其他測量僅為相對較少的有效數字所知,因此整個問題空間反正是“不精確”。FP“準確度”在這種應用中不是問題。

當人們嘗試使用FP進行bean計數時,整個問題就出現了。它確實起作用,但只有當你堅持使用積分值時,才會失去使用它的點。這就是我們擁有所有小數部分軟件庫的原因。

我喜歡的比薩答案,因為它描述了實際的問題,而不僅僅是關於“不准確”的常見問題。如果FP只是“不准確”,我們可以解決這個問題並且幾十年前就已經完成了。我們之所以沒有這個原因,是因為FP格式緊湊而且速度快,而且它是壓縮大量數字的最佳方式。此外,它是太空時代和軍備競賽的遺產,也是使用小型內存系統解決大型問題的早期嘗試。 (有時,單個磁芯用於1位存儲,但這是另一個故事。

結論

如果您只是計算銀行的bean,那麼首先使用十進製字符串表示的軟件解決方案可以很好地工作。但你不能用那種方式做量子色動力學或空氣動力學。


這裡的大多數答案都以非常乾燥的技術術語來解決這個問題 我想以正常人能夠理解的方式來解決這個問題。

想像一下,你正在嘗試切片比薩餅。 你有一個機器人披薩刀,可以切成兩半的披薩片。 它可以將整個披薩減半,或者它可以將現有切片減半,但無論如何,減半總是精確的。

那個披薩刀具有非常精細的動作,如果你從整個披薩開始,然後將其減半,並且每次繼續減半最小的切片,你可以在切片太小之前減半53次 ,即使它的高精度能力也是如此。 此時,您不能再將那個非常薄的切片減半,但必須按原樣包含或排除它。

現在,你將如何將所有切片分成幾乎十分之一(0.1)或五分之一(0.2)的披薩? 真的想一想,試試吧。 如果您手邊有神話般的精密披薩刀,您甚至可以嘗試使用真正的披薩。 :-)

當然,大多數有經驗的程序員都知道真正的答案,即無論你如何精細切片,都無法使用這些切片拼湊出十分之一或五分之一的披薩。 你可以做一個非常好的近似,如果你用近似值0.2加上0.1的近似值,你會得到0.3的近似值,但它仍然只是一個近似值。

對於雙精度數字(這是允許您將披薩減半53倍的精度),立即小於和大於0.1的數字是0.09999999999999999167332731531132594682276248931884765625和0.1000000000000000055511151231257827021181583404541015625。 後者比前者更接近0.1,因此如果輸入為0.1,則數字解析器將支持後者。

(這兩個數字之間的差異是我們必須決定要包括的“最小切片”,它引入向上偏差,或排除,這會引入向下偏差。最小切片的技術術語是ulp 。)

在0.2的情況下,數字都是相同的,只是按比例增加了2倍。再次,我們贊成略高於0.2的值。

請注意,在這兩種情況下,0.1和0.2的近似值都略有向上偏差。 如果我們添加足夠的這些偏差,它們會使數字越來越遠離我們想要的數字,事實上,在0.1 + 0.2的情況下,偏差足夠高,結果數字不再是最接近的數字到0.3。

特別是,0.1 + 0.2實際上是0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125,而最接近0.3的數字實際上是0.299999999999999988897769753748434595763683319091796875。

PS一些編程語言還提供披薩切割器,可以將切片分成精確的十分之一 。 雖然這種披薩切割器並不常見,但如果您確實可以使用它,那麼在重要的是能夠獲得切片的十分之一或五分之一時,應該使用它。

(最初發佈在Quora上。)


Math.sum(javascript)....運算符更換

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

我們的想法是使用Math而不是運算符來避免浮點錯誤

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

另請注意,Math.diff和Math.sum會自動檢測要使用的精度

Math.sum接受任意數量的參數


一些有關這個著名的雙精度問題的統計數據。

當使用0.1(從0.1到100)的步長添加所有值(a + b)時,我們有大約15%的精度誤差的可能性。請注意,錯誤可能會導致稍大或稍小的值。這裡有些例子:

0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)

當使用0.1(從100到0.1)的步長減去所有值(a-b,其中a> b)時,我們有大約34%的精度誤差。這裡有些例子:

0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)

* 15%和34%確實很大,所以當精度非常重要時,請始終使用BigDecimal。使用2位十進制數字(步驟0.01),情況會惡化一些(18%和36%)。


一個不同的問題已被命名為此問題的副本:

在C ++中,為什麼結果與cout << x調試器顯示的值不同x

x在的問題是一個float變量。

一個例子是

float x = 9.9F;

調試器顯示9.89999962cout操作的輸出是9.9

答案結果cout是默認精度為float6,因此它舍入為6位小數。

請參閱here以供參考


你嘗試過膠帶解決方案了嗎?

嘗試確定何時發生錯誤並使用短if語句修復它們,它並不漂亮,但對於某些問題,它是唯一的解決方案,這是其中之一。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

我在c#的科學模擬項目中遇到了同樣的問題,我可以告訴你,如果你忽略了蝴蝶效應,它會變成一條巨大的肥龍並咬你的**


只是為了好玩,我按照標準C99的定義玩了浮點數的表示,我編寫了下面的代碼。

代碼在3個分隔的組中打印浮動的二進製表示

SIGN EXPONENT FRACTION

然後打印一個總和,當它與足夠的精度相加時,它將顯示硬件中真正存在的值。

因此,當您編寫時float x = 999...,編譯器將在函數打印的位表示中轉換該數字,使得函數xx打印的總和yy等於給定的數字。

實際上,這個總和只是一個近似值。對於數字999,999,999,編譯器將以浮點數的形式插入數字1,000,000,000

在代碼之後,我附加了一個控制台會話,其中我計算了兩個常量(減去PI和999999999)的術語總和,它們實際存在於硬件中,由編譯器插入。

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

這是一個控制台會話,我在其中計算硬件中存在的浮點的實際值。我曾經bc打印過主程序輸出的術語總和。人們可以在python repl或類似的東西中插入這個總和。

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

而已。 事實上,值999999999

999999999.999999446351872

你也可以檢查bc-3.14也是擾亂。不要忘記設置一個scale因素bc

顯示的總和是硬件內部的內容。通過計算得到的值取決於您設置的比例。我確實將scale因子設置為15.數學上,無限精度,似乎是1,000,000,000。


已發布了許多好的答案,但我想再追加一個。

並非所有數字都可以通過浮點數 / 數來表示例如,數字“0.2”將在IEEE754浮點標準中以單精度表示為“0.200000003”。

引擎蓋下的商店實數模型表示浮點數

即使你可以0.2輕鬆打字,FLT_RADIX而且DBL_RADIX是2; 對於具有FPU的計算機,使用“IEEE二進制浮點運算標準(ISO / IEEE Std 754-1985)”不是10。

因此,準確表示這些數字有點困難。即使您明確指定此變量而沒有任何中間計算。


我的解決方法:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

precision是指在添加期間小數點後要保留的位數。


由於這個線程分解為對當前浮點實現的一般討論,我補充說有一些項目可以修復它們的問題。

https://posithub.org/為例,它展示了一個名為posit(及其前身unum)的數字類型,它承諾以更少的位提供更好的精度。如果我的理解是正確的,它也解決了問題中的問題。非常有趣的項目,其背後的人是John Gustafson博士的數學家。整個過程都是開源的,在C / C ++,Python,Julia和C#(https://hastlayer.com/arithmetics)中有許多實際的實現。


當您將.1或1/10轉換為基數2(二進制)時,您會在小數點後得到重複模式,就像嘗試在基數10中表示1/3一樣。值不准確,因此您無法做到使用常規浮點方法精確數學。


鑑於沒有人提到這個......

一些高級語言(如Python和Java)提供了克服二進制浮點限制的工具。 例如:

  • Python的decimal模塊和Java的BigDecimal,用十進製表示法在內部表示數字(與二進製表示法相對)。兩者都具有有限的精度,因此它們仍然容易出錯,但是它們解決了二進制浮點算法的最常見問題。

    處理貨幣時小數點非常好:十美分外加二十美分總是正好三十美分:

    >>> 0.1 + 0.2 == 0.3
    False
    >>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
    True
    

    Python的decimal模塊基於IEEE標準854-1987

  • Python的fractions模塊和Apache Common的BigFraction。兩者都將有理數表示為(numerator, denominator)對,並且它們可以給出比十進制浮點算法更準確的結果。

這些解決方案都不是完美的(特別是如果我們看一下性能,或者我們需要非常高的精度),但它們仍然解決了二進制浮點運算的大量問題。


除了其他正確答案之外,您可能還需要考慮縮放值以避免浮點運算出現問題。

例如:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... 代替:

var result = 0.1 + 0.2;     // result === 0.3 returns false

表達式0.1 + 0.2 === 0.3在JavaScript中返回false ,但幸運的是浮點中的整數運算是精確的,因此可以通過縮放來避免十進製表示錯誤。

作為一個實際的例子,為了避免準確性至關重要的浮點問題,建議1處理貨幣作為表示分數的整數: 2550美分而不是25.50美元。

1 Douglas Crockford: JavaScript:好的部分 :附錄A - 可怕的部分(第105頁)


可以在數字計算機中實現的浮點數學類型必然使用實數和操作的近似值。(標準版本可以運行超過五十頁的文檔,並有一個委員會來處理其勘誤和進一步改進。)

這種近似是不同種類近似的混合,由於其與精確度的偏差的特定方式,每種近似可以被忽略或仔細考慮。它還涉及硬件和軟件級別的一些明顯的例外情況,大多數人在假裝不注意的情況下走過去。

如果你需要無限精度(例如,使用數字π代替其中許多較短的替身之一),你應該編寫或使用符號數學程序。

但是,如果您認為有時候浮點數學在價值和邏輯上是模糊的並且錯誤可以快速累積,並且您可以編寫您的需求和測試來實現這一點,那麼您的代碼可以經常得到什麼在你的FPU。


我可以添加; 人們總是認為這是一個計算機問題,但如果你用你的手數(基數10),(1/3+1/3=2/3)=true除非你有無窮大增加0.333 ......到0.333 ......否則你無法得到...所以就像(1/10+2/10)!==3/10基地的問題一樣2,你將它截斷為0.333 + 0.333 = 0.666並可能將其四捨五入到0.667,這在技術上也是不准確的。

數三進制,三分之一不是問題 - 也許每手上有15個手指的比賽會問為什麼你的十進制數學被打破了...


浮點舍入錯誤。從每個計算機科學家應該知道的浮點運算

將無限多個實數壓縮成有限數量的比特需要近似表示。儘管存在無限多個整數,但在大多數程序中,整數計算的結果可以以32位存儲。相反,給定任何固定數量的位,大多數具有實數的計算將產生無法使用那麼多位精確表示的量。因此,浮點計算的結果通常必須舍入,以便適應其有限表示。該舍入誤差是浮點計算的特徵。


為了提供最好的解決方案,我可以說我發現了以下方法:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

讓我解釋一下為什麼它是最好的解決方案。正如上面提到的其他人所提到的,使用現成的Javascript toFixed()函數解決問題是個好主意。但很可能你會遇到一些問題。

想像一下,你要加上兩個漂浮數字0.20.7這裡是:0.2 + 0.7 = 0.8999999999999999

您的預期結果0.9意味著在這種情況下您需要一位數精度的結果。所以你應該使用(0.2 + 0.7).tofixed(1)但是你不能只給toFixed()一個參數,因為它取決於給定的數字,例如

`0.22 + 0.7 = 0.9199999999999999`

在這個例子中,你需要2位數的精度,所以它應該是toFixed(2)什麼,所以適合每個給定的浮點數的參數是什麼?

你可能會說在每種情況下都是10:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

該死的!在9點之後你打算用那些不需要的零做什麼?現在是時候將它轉換為浮動,以便按照您的意願製作它:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

現在您找到了解決方案,最好將其作為這樣的函數提供:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

我們自己嘗試一下:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

你可以這樣使用它:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

正如W3SCHOOLS建議的還有其他解決方案,你可以乘以除以解決上面的問題:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

請記住,(0.2 + 0.1) * 10 / 10雖然看起來一樣,但根本不起作用!我更喜歡第一種解決方案,因為我可以將它應用為將輸入浮點轉換為精確輸出浮點數的函數。





floating-accuracy