簡體   English   中英

浮點加法和乘法是結合的嗎?

[英]Is floating-point addition and multiplication associative?

我在添加三個浮點值並將它們與 1 進行比較時遇到了問題。

cout << ((0.7 + 0.2 + 0.1)==1)<<endl;     //output is 0
cout << ((0.7 + 0.1 + 0.2)==1)<<endl;     //output is 1

為什么這些值會不同?

浮點加法不一定是結合的。 如果您更改添加內容的順序,這可能會改變結果。

關於這個主題的標准論文是 每個計算機科學家應該知道的關於浮點運算的知識 它給出了以下示例:

另一個灰色區域涉及括號的解釋。 由於舍入誤差,代數的結合律不一定適用於浮點數。 例如,當 x = 1e30、y = -1e30 和 z = 1 時,表達式 (x+y)+z 與 x+(y+z) 的答案完全不同(前者為 1,后者為 0 )。

使用當前流行的機器和軟件,可能的是:

編譯器編碼的.7作為0x1.6666666666666p-1(這是乘以2至-1的功率十六進制數字1.6666666666666), .2作為0x1.999999999999ap-3和.1作為0x1.999999999999ap-4。 其中每一個都是可表示為最接近您所寫的十進制數字的浮點數。

觀察到這些十六進制浮點常量中的每一個在其有效數(“分數”部分,通常不准確地稱為尾數)中都恰好有 53 位。 有效數的十六進制數字有一個“1”和另外 13 個十六進制數字(每個 4 位,總共 52 位,包括“1”在內的 53 位),這是 IEEE-754 標准規定的,用於 64 位二進制浮點數。點數。

讓我們添加.7.2的數字:0x1.6666666666666p-1 和 0x1.999999999999ap-3。 首先,縮放第二個數字的指數以匹配第一個數字。 為此,我們將指數乘以 4(將“p-3”更改為“p-1”)並將有效數乘以 1/4,得到 0x0.666666666666668p-1。 然后添加 0x1.6666666666666p-1 和 0x0.66666666666668p-1,得到 0x1.ccccccccccccc8p-1。 請注意,此數字的有效數位超過 53 位:“8”是句點后的第 14 位。 浮點不能返回這么多位的結果,所以它必須四舍五入到最接近的可表示數。 在這種情況下,有兩個同樣接近的數字,0x1.cccccccccccccp-1 和 0x1.ccccccccccccdp-1。 當出現平局時,使用在有效數的最低位中帶有零的數字。 “c”是偶數,“d”是奇數,所以使用“c”。 添加的最終結果是0x1.cccccccccccccp-1。

接下來,將.1 (0x1.999999999999ap-4) 的數字添加到其中。 同樣,我們縮放以使指數匹配,因此 0x1.999999999999ap-4 變為 0x.33333333333334p-1。 然后將其添加到 0x1.cccccccccccccp-1,給出 0x1.ffffffffffffff4p-1。 將其四舍五入為 53 位給出 0x1.fffffffffffffp-1,這是.7+.2+.1的最終結果。

現在考慮.7+.1+.2 對於.7+.1 ,添加 0x1.6666666666666p-1 和 0x1.999999999999ap-4。 回想一下,后者被縮放到 0x.33333333333334p-1。 那么確切的總和是 0x1.99999999999994p-1。 將其四舍五入為 53 位給出 0x1.9999999999999p-1。

然后添加.2 (0x1.999999999999ap-3) 的數字,將其縮放為 0x0.66666666666668p-1。 確切的總和是 0x2.00000000000008p-1。 浮點有效數總是從 1 開始縮放(特殊情況除外:零、無窮大和可表示范圍底部的非常小的數字),因此我們將其調整為 0x1.00000000000004p0。 最后,我們四舍五入到 53 位,得到 0x1.0000000000000p0。

因此,由於舍入時發生錯誤, .7+.2+.1返回 0x1.fffffffffffffp-1(非常略小於 1),而.7+.1+.2返回 0x1.0000000000000p0(正好是 1)。

浮點乘法在 C 或 C++ 中不是關聯的。

證明:

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
using namespace std;
int main() {
    int counter = 0;
    srand(time(NULL));
    while(counter++ < 10){
        float a = rand() / 100000;
        float b = rand() / 100000;
        float c = rand() / 100000;

        if (a*(b*c) != (a*b)*c){
            printf("Not equal\n");
        }
    }
    printf("DONE");
    return 0;
}

在這個程序中,大約 30% 的時間(a*b)*c不等於a*(b*c)

與 Eric 的類似答案,但用於添加和使用 Python。

import random

random.seed(0)
n = 1000
a = [random.random() for i in range(n)]
b = [random.random() for i in range(n)]
c = [random.random() for i in range(n)]

sum(1 if (a[i] + b[i]) + c[i] != a[i] + (b[i] + c[i]) else 0 for i in range(n))

加法和乘法都不與 IEEE 743 雙精度(64 位)數相關聯。 以下是每個示例(使用 Python 3.9.7 評估):

>>> (.1 + .2) + .3
0.6000000000000001
>>> .1 + (.2 + .3)
0.6
>>> (.1 * .2) * .3
0.006000000000000001
>>> .1 * (.2 * .3)
0.006

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM