>> 0.1 + 0.2 0.30000000000000004 我意識到我并沒有完全理解它是如何計算的。我的意思是,我知道浮點計算是不精確的,你不能精確地用二進制表示?0.1,但是:肯定有一個浮點數(shù)比?0.30000000000000004?更接近 0.3!那為什么答案是?0.30000000000000004?呢? 如果你不想閱讀一大堆計算過程,那么簡短的答案是:?0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102" />

那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

浮點加法是如何計算的

dyquk4xk2p3d ? 來源:良許Linux ? 2023-05-26 15:26 ? 次閱讀

嗨!我試著寫點關(guān)于浮點數(shù)的東西,我發(fā)現(xiàn)自己對這個 64 位浮點數(shù)的計算方法很好奇:


>>> 0.1 + 0.2

0.30000000000000004

我意識到我并沒有完全理解它是如何計算的。我的意思是,我知道浮點計算是不精確的,你不能精確地用二進制表示0.1,但是:肯定有一個浮點數(shù)比0.30000000000000004更接近 0.3!那為什么答案是0.30000000000000004呢?

如果你不想閱讀一大堆計算過程,那么簡短的答案是:0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125正好位于兩個浮點數(shù)之間,即0.299999999999999988897769753748434595763683319091796875(通常打印為0.3) 和0.3000000000000000444089209850062616169452667236328125(通常打印為0.30000000000000004)。答案是0.30000000000000004,因為它的尾數(shù)是偶數(shù)。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

浮點加法是如何計算的

以下是浮點加法的簡要計算原理:

?把它們精確的數(shù)字加在一起 ?將結(jié)果四舍五入到最接近的浮點數(shù)

讓我們用這些規(guī)則來計算 0.1 + 0.2。我昨天才剛了解浮點加法的計算原理,所以在這篇文章中我可能犯了一些錯誤,但最終我得到了期望的答案。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

第一步:0.1 和 0.2 到底是多少

首先,讓我們用 Python 計算0.1和0.2的 64 位浮點值。


>>> f"{0.1:.80f}"

'0.10000000000000000555111512312578270211815834045410156250000000000000000000000000'

>>> f"{0.2:.80f}"

'0.20000000000000001110223024625156540423631668090820312500000000000000000000000000'

這確實很精確:因為浮點數(shù)是二進制的,你也可以使用十進制來精確的表示。但有時你只是需要一大堆數(shù)字:)

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

第二步:相加

接下來,把它們加起來。我們可以將小數(shù)部分作為整數(shù)加起來得到確切的答案:


>>> 1000000000000000055511151231257827021181583404541015625 + 2000000000000000111022302462515654042363166809082031250

3000000000000000166533453693773481063544750213623046875

所以這兩個浮點數(shù)的和是0.3000000000000000166533453693773481063544750213623046875。

但這并不是最終答案,因為它不是一個 64 位浮點數(shù)。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

第三步:查找最接近的浮點數(shù)

現(xiàn)在,讓我們看看接近0.3的浮點數(shù)。下面是最接近0.3的浮點數(shù)(它通常寫為0.3,盡管它不是確切值):


>>> f"{0.3:.80f}"

'0.29999999999999998889776975374843459576368331909179687500000000000000000000000000'

我們可以通過struct.pack將0.3序列化為 8 字節(jié)來計算出它之后的下一個浮點數(shù),加上 1,然后使用struct.unpack:


>>> struct.pack("!d", 0.3)

b'?xd3333333'

# 手動加 1

>>> next_float = struct.unpack("!d", b'?xd3333334')[0]

>>> next_float

0.30000000000000004

>>> f"{next_float:.80f}"

'0.30000000000000004440892098500626161694526672363281250000000000000000000000000000'

當然,你也可以用math.nextafter:


>>> math.nextafter(0.3, math.inf)

0.30000000000000004

所以0.3附近的兩個 64 位浮點數(shù)是0.299999999999999988897769753748434595763683319091796875和0.3000000000000000444089209850062616169452667236328125。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

第四步:找出哪一個最接近

結(jié)果證明0.3000000000000000166533453693773481063544750213623046875正好在0.299999999999999988897769753748434595763683319091796875和0.3000000000000000444089209850062616169452667236328125的中間。

你可以通過以下計算看到:


>>> (3000000000000000444089209850062616169452667236328125000 + 2999999999999999888977697537484345957636833190917968750) // 2 == 3000000000000000166533453693773481063544750213623046875

True

所以它們都不是最接近的。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

如何知道四舍五入到哪一個?

在浮點數(shù)的二進制表示中,有一個數(shù)字稱為“尾數(shù)”。這種情況下(結(jié)果正好在兩個連續(xù)的浮點數(shù)之間),它將四舍五入到偶數(shù)尾數(shù)的那個。

在本例中為0.300000000000000044408920985006261616945266723632812500。

我們之前就見到了這個數(shù)字的尾數(shù):

?0.30000000000000004 是struct.unpack('!d', b'?xd3333334')的結(jié)果 ?0.3 是struct.unpack('!d', b'?xd3333333')的結(jié)果

0.30000000000000004的大端十六進制表示的最后一位數(shù)字是4,它的尾數(shù)是偶數(shù)(因為尾數(shù)在末尾)。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

我們用二進制來算一下

之前我們都是使用十進制來計算的,這樣讀起來更直觀。但是計算機并不會使用十進制,而是用 2 進制,所以我想知道它是如何計算的。

我不認為本文的二進制計算部分特別清晰,但它寫出來對我很有幫助。有很多數(shù)字,讀起來可能很糟糕。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

64 位浮點數(shù)如何計算:指數(shù)和尾數(shù)

64 位浮點數(shù)由 2 部分整數(shù)構(gòu)成:指數(shù)和尾數(shù),還有 1 比特符號位.

以下是指數(shù)和尾數(shù)對應(yīng)于實際數(shù)字的方程:

7902e3d2-fae3-11ed-90ce-dac502259ad0.jpg

例如,如果指數(shù)是1,尾數(shù)是2**51,符號位是正的,那么就可以得到:

7908885a-fae3-11ed-90ce-dac502259ad0.jpg

它等于2 * (1 + 0.5),即 3。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

步驟 1:獲取 0.1 和 0.2 的指數(shù)和尾數(shù)

我用 Python 編寫了一些低效的函數(shù)來獲取正浮點數(shù)的指數(shù)和尾數(shù):


def get_exponent(f):

# 獲取前 52 個字節(jié)

bytestring = struct.pack('!d', f)

return int.from_bytes(bytestring, byteorder='big') >> 52

def get_significand(f):

# 獲取后 52 個字節(jié)

bytestring = struct.pack('!d', f)

x = int.from_bytes(bytestring, byteorder='big')

exponent = get_exponent(f)

return x ^ (exponent << 52)

我忽略了符號位(第一位),因為我們只需要處理 0.1 和 0.2,它們都是正數(shù)。

首先,讓我們獲取 0.1 的指數(shù)和尾數(shù)。我們需要減去 1023 來得到實際的指數(shù),因為浮點運算就是這么計算的。


>>> get_exponent(0.1) - 1023

-4

>>> get_significand(0.1)

2702159776422298

它們根據(jù)2**指數(shù) + 尾數(shù) / 2**(52 - 指數(shù))這個公式得到0.1。

下面是 Python 中的計算:


>>> 2**-4 + 2702159776422298 / 2**(52 + 4)

0.1

(你可能會擔心這種計算的浮點精度問題,但在本例中,我很確定它沒問題。因為根據(jù)定義,這些數(shù)字沒有精度問題 -- 從2**-4開始的浮點數(shù)以1/2**(52 + 4)步長遞增。)

0.2也一樣:


>>> get_exponent(0.2) - 1023

-3

>>> get_significand(0.2)

2702159776422298

它們共同工作得到0.2:


>>> 2**-3 + 2702159776422298 / 2**(52 + 3)

0.2

(順便說一下,0.1 和 0.2 具有相同的尾數(shù)并不是巧合 —— 因為x和2*x總是有相同的尾數(shù)。)

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

步驟 2:重新計算 0.1 以獲得更大的指數(shù)

0.2的指數(shù)比0.1大 -- -3 大于 -4。

所以我們需要重新計算:


2**-4 + 2702159776422298 / 2**(52 + 4)

等于X / (2**52 + 3)

如果我們解出2**-4 + 2702159776422298 / 2**(52 + 4) = X / (2**52 + 3),我們能得到:

X = 2**51 + 2702159776422298 /2

在 Python 中,我們很容易得到:


>>> 2**51 + 2702159776422298 //2

3602879701896397

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

步驟 3:添加符號位

現(xiàn)在我們試著做加法:


2**-3 + 2702159776422298 / 2**(52 + 3) + 3602879701896397 / 2**(52 + 3)

我們需要將2702159776422298和3602879701896397相加:


>>> 2702159776422298 + 3602879701896397

6305039478318695

棒。但是6305039478318695比2**52-1(尾數(shù)的最大值)大,問題來了:


>>> 6305039478318695 > 2**52

True

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

第四步:增加指數(shù)

目前結(jié)果是:


2**-3 + 6305039478318695 / 2**(52 + 3)

首先,它減去 2**52:


2**-2 + 1801439850948199 / 2**(52 + 3)

完美,但最后的2**(52 + 3)需要改為2**(52 + 2)。

我們需要將1801439850948199除以 2。這就是難題的地方 --1801439850948199是一個奇數(shù)!


>>> 1801439850948199 / 2

900719925474099.5

它正好在兩個整數(shù)之間,所以我們四舍五入到最接近它的偶數(shù)(這是浮點運算規(guī)范要求的),所以最終的浮點結(jié)果是:


>>> 2**-2 + 900719925474100 / 2**(52 + 2)

0.30000000000000004

它就是我們預(yù)期的結(jié)果:


>>> 0.1 + 0.2

0.30000000000000004

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

在硬件中它可能并不是這樣工作的

在硬件中做浮點數(shù)加法,以上操作方式可能并不完全一模一樣(例如,它并不是求解 “X”),我相信有很多有效的技巧,但我認為思想是類似的。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

打印浮點數(shù)是非常奇怪的

我們之前說過,浮點數(shù) 0.3 不等于 0.3。它實際上是:


>>> f"{0.3:.80f}"

'0.29999999999999998889776975374843459576368331909179687500000000000000000000000000'

但是當你打印它時,為什么會顯示0.3?

計算機實際上并沒有打印出數(shù)字的精確值,而是打印出了最短的十進制數(shù)d,其中f是最接近d的浮點數(shù)。

事實證明,有效做到這一點很不簡單,有很多關(guān)于它的學術(shù)論文,比如快速且準確地打印浮點數(shù) legacy.cs.indiana.edu、如何準確打印浮點數(shù) lists.nongnu.org等。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

如果計算機打印出浮點數(shù)的精確值,會不會更直觀一些?

四舍五入到一個干凈的十進制值很好,但在某種程度上,我覺得如果計算機只打印一個浮點數(shù)的精確值可能會更直觀 -- 當你得到一個奇怪的結(jié)果時,它可能會讓你看起來不那么驚訝。

對我來說,0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125比0.1 + 0.2 = 0.30000000000000000004驚訝少一點。

這也許是一個壞主意,因為它肯定會占用大量的屏幕空間。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

PHP 快速說明

有人在評論中指出在 PHP 中會輸出0.3,這是否說明在 PHP 中浮點運算不一樣?

非也 —— 我在這里 replit.com運行:

,得到了與 Python 完全相同的答案:5.5511151231258E-17。因此,浮點運算的基本原理是一樣的。

我認為在 PHP 中0.1 + 0.2輸出0.3的原因是 PHP 顯示浮點數(shù)的算法沒有 Python 精確 —— 即使這個數(shù)字不是最接近 0.3 的浮點數(shù),它也會顯示0.3。

78c8f172-fae3-11ed-90ce-dac502259ad0.svg

總結(jié)

我有點懷疑是否有人能耐心完成以上所有些算術(shù),但它寫出來對我很有幫助,所以我還是發(fā)表了這篇文章,希望它能有所幫助。

審核編輯:彭靜
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 浮點數(shù)
    +關(guān)注

    關(guān)注

    0

    文章

    61

    瀏覽量

    15909
  • PHP
    PHP
    +關(guān)注

    關(guān)注

    0

    文章

    454

    瀏覽量

    26788

原文標題:為什么 0.1 + 0.2 = 0.30000000000000004?

文章出處:【微信號:良許Linux,微信公眾號:良許Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    verilog語音實現(xiàn)浮點運算

    Verilog可以通過使用IEEE標準的浮點數(shù)表示來實現(xiàn)浮點運算。下面是一個基本的Verilog模塊示例,展示了如何進行加法、乘法和除法等常見的浮點運算操作: module
    發(fā)表于 03-25 21:49

    32位浮點加法器設(shè)計

    求助誰幫我設(shè)計一個32位浮點加法器,求助啊,謝謝啊 新搜剛學verilog,不會做{:4_106:}
    發(fā)表于 10-20 20:07

    為什么研究浮點加法運算,對FPGA實現(xiàn)方法很有必要?

    現(xiàn)代信號處理技術(shù)通常都需要進行大量高速浮點運算。由于浮點數(shù)系統(tǒng)操作比較復(fù)雜,需要專用硬件來完成相關(guān)的操作(在浮點運算中的浮點加法運算幾乎占到
    發(fā)表于 07-05 06:21

    如何利用FPGA實現(xiàn)高速流水線浮點加法器研究?

    現(xiàn)代信號處理技術(shù)通常都需要進行大量高速浮點運算。由于浮點數(shù)系統(tǒng)操作比較復(fù)雜,需要專用硬件來完成相關(guān)的操作(在浮點運算中的浮點加法運算幾乎占到
    發(fā)表于 08-15 08:00

    求一種在FPGA上實現(xiàn)單精度浮點加法運算的方法

    介紹一種在FPGA上實現(xiàn)的單精度浮點加法運算器,運算器算法的實現(xiàn)考慮了FPGA器件本身的特點,算法處理流程的拆分和模塊的拆分,便于流水設(shè)計的實現(xiàn)。
    發(fā)表于 04-29 06:27

    請問一下高速流水線浮點加法器的FPGA怎么實現(xiàn)?

    請問一下高速流水線浮點加法器的FPGA怎么實現(xiàn)?
    發(fā)表于 05-07 06:44

    如何對計算加法電路進行proteus仿真呢

    計算加法的電路原理是什么?如何對計算加法電路進行proteus仿真呢?
    發(fā)表于 11-01 07:00

    高速流水線浮點加法器的FPGA實現(xiàn)

    高速流水線浮點加法器的FPGA實現(xiàn) 0  引言現(xiàn)代信號處理技術(shù)通常都需要進行大量高速浮點運算。由于浮點數(shù)系統(tǒng)操作比較復(fù)雜,需要專用硬件來完成相關(guān)的操
    發(fā)表于 02-04 10:50 ?2418次閱讀
    高速流水線<b class='flag-5'>浮點</b><b class='flag-5'>加法</b>器的FPGA實現(xiàn)

    補碼加法,補碼加法計算原理

    補碼加法,補碼加法計算原理    負數(shù)用補碼表示后,可以和正數(shù)一樣來處理。這樣,運算器里只需要一個加法器就可以了,不必為了負數(shù)的加法運算,再
    發(fā)表于 04-13 11:41 ?1.8w次閱讀

    FPU加法器的設(shè)計與實現(xiàn)

    浮點運算器的核心運算部件是浮點加法器,它是實現(xiàn)浮點指令各種運算的基礎(chǔ),其設(shè)計優(yōu)化對于提高浮點運算的速度和精度相當關(guān)鍵。文章從
    發(fā)表于 07-06 15:05 ?47次下載
    FPU<b class='flag-5'>加法</b>器的設(shè)計與實現(xiàn)

    十進制加法計算

    Multisim 仿真作業(yè) 一位十進制加法計算器。
    發(fā)表于 04-25 14:22 ?0次下載

    同相加法器電路原理與同相加法計算

    同相加法器輸入阻抗高,輸出阻抗低 反相加法器輸入阻抗低,輸出阻抗高.加法器是一種數(shù)位電路,其可進行數(shù)字的加法計算。當選用同相
    發(fā)表于 09-13 17:23 ?5.8w次閱讀
    同相<b class='flag-5'>加法</b>器電路原理與同相<b class='flag-5'>加法</b>器<b class='flag-5'>計算</b>

    浮點運算單元的FPGA實現(xiàn)

    浮點加法是數(shù)字信號處理中的一種非常頻繁且非常重要的操作,在現(xiàn)代數(shù)字信號處理應(yīng)用中,浮點加法運算幾乎占到全部浮點操作的一半以上。
    發(fā)表于 04-10 10:47 ?8次下載
    <b class='flag-5'>浮點</b>運算單元的FPGA實現(xiàn)

    怎么設(shè)計一個32bit浮點加法器呢?

    設(shè)計一個32bit浮點加法器,out = A + B,假設(shè)AB均為無符號位,或者換個說法都為正數(shù)。
    的頭像 發(fā)表于 06-02 16:13 ?1395次閱讀
    怎么設(shè)計一個32bit<b class='flag-5'>浮點</b>的<b class='flag-5'>加法</b>器呢?

    為什么研究浮點加法運算,對FPGA實現(xiàn)方法很有必要?

    ,浮點加法器是現(xiàn)代信號處理系統(tǒng)中最重要的部件之一。FPGA是當前數(shù)字電路研究開發(fā)的一種重要實現(xiàn)形式,它與全定制ASIC電路相比,具有開發(fā)周期短、成本低等優(yōu)點。 但多數(shù)FPGA不支持浮點運算,這使FPGA在數(shù)值
    的頭像 發(fā)表于 09-22 10:40 ?1226次閱讀
    為什么研究<b class='flag-5'>浮點</b><b class='flag-5'>加法</b>運算,對FPGA實現(xiàn)方法很有必要?
    澳门百家乐职业赌客| 泰安市| 捷豹百家乐官网娱乐城| 大发888娱乐平台 游戏| 粤港澳百家乐官网娱乐| 大发888官方 hdlsj| 太阳百家乐官网管理网| bet365资讯网| 上海百家乐官网的玩法技巧和规则 | 太阳城百家乐官网注册平台| 东莞百家乐的玩法技巧和规则| 澳门1百家乐官网网| 大发888娱乐城客服| 皇冠网百家乐官网阿| 雷山县| 百家乐博娱乐网赌百家乐的玩法技巧和规则| 赌博百家乐官网探讨| 大发888官方sscptdf88yb| 24山向方位| 金华市| 威尼斯人娱乐城代理注册| 金域百家乐官网的玩法技巧和规则| 明升| 真博百家乐的玩法技巧和规则| 送现金百家乐官网的玩法技巧和规则 | 百家乐平台有什么优惠| 永利百家乐官网娱乐场| 线上娱乐场| 水晶百家乐筹码| 金百家乐官网网站| 天天乐娱乐| 澳门百家乐网络游戏信誉怎么样| 百家乐官网和的打法| 聚宝盆百家乐官网游戏| 大发888游戏免费下载| 百家乐如何打公式| 百家乐官网网上投注文章| 大发888官网是多少| 百家乐智能分析| 全迅网百家乐官网的玩法技巧和规则 | 百家乐官网赌博论谈|