📜 [專欄新文章] Uniswap 解析:恆定乘積做市商模型 Constant Product Market Maker Model 的 Vyper 實作
✍️ 田少谷 Shao
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
在 🦄 Uniswap v2 到來之前徹底了解 v1 的設計與演算法!
Image source: https://uniswap.org/
Outline
一. 前言二. 恆定乘積做市商模型 Constant Product Market Maker Model 1. 計入手續費 2. 程式碼結構 3. 演算法核心與實作 4. 段落小結三. 流動性 Liquidity 1. 第一筆流動性注入、決定k值 2. 除了第一筆以外的情況四. 結語
一. 前言
暨上一篇開始接觸了 Vyper 後,我找了 Uniswap 的程式碼來更加熟悉 Vyper 的實作方法,順便研究了其演算法,然後就又寫了一篇 xD
類 Python 的合約語言 Vyper 開發入門:與 Solidity 差異、用 Truffle 部署、ERC20 賣幣合約實做
Uniswap 是以太坊上非常成功的自動做市商 Automated Market Maker (AMM)。本次我將用的 Uniswap 的程式碼搭配由 Runtime Verification 這家審計公司對 Uniswap 所做的形式化驗證結果來解釋恆定乘積做市商模型的 Vyper 實作 (2018 審計時 Uniswap 就已經是用 Vyper 而非 Solidity 了):
智能合約程式碼:https://github.com/Uniswap/uniswap-v1/blob/master/contracts/uniswap_exchange.vy
合約審計結果:https://github.com/runtimeverification/verified-smart-contracts/blob/master/uniswap/x-y-k.pdf
本文將以講解實作概念及數學推導為重點,程式碼的部分只是輔助。審計結果將恆定乘積做市商模型演算法的數學推導寫得非常清楚而有趣(?),建議有興趣者可以整份看過一遍,相信得到很多收穫!
至於更多 Uniswap 的介紹有興趣者可以參考 吳冠融 Roger Wu 所撰寫的簡介與使用流程:
解析 DeFi 項目《Uniswap》(一)Uniswap 是什麼?
解析 DeFi 項目《Uniswap》(二)Uniswap 如何使用?
在開始前的最後,先預告本文頗長,所以來播個被 Youtube 推薦的歌吧:
二. 恆定乘積做市商模型 Constant Product Market Maker Model
交易所如果要去中心化、也不使用掛單 order book,就需要靠演算法自動算出交易標的的數量與價格,而 Uniswap 使用名為恆定乘積的演算法,其來源可追溯自 Vitalik 的這篇文章:點我。
公式非常的簡單:x * y = k。令交易的兩虛擬貨幣為 X 和 Y,各自數量為 x 和 y,兩貨幣數量的乘積 x * y 恆等於 k,k 值是由第一筆注入的流動性所決定 (於 三. 流動性 Liquidity 解釋)。
因此,用 ∆x 數量的 X 幣來購買 Y 幣所能得到的數量 ∆y、或是為了購買 ∆y 需要付出的 ∆x 數量,依照此公式進行計算:(x+∆x)(y-∆y) = k,而交易的價格就是兩幣量 ∆x 和 ∆y 的比。
以下公式用 α = ∆x / x 和 β = ∆y / y 來表示 ∆x 和 ∆y 及 X Y 兩幣在交易發生後的新均衡數量:
圖一
1. 計入手續費
在 Uniswap 進行的每一筆交易都會被收取 ρ = 0.003 / 0.3% 的手續費回饋給流動性提供者 liquidity provider ,因此要將手續費納入公式的考量:
圖二
上圖的公式或許不太直覺,我建議不要從 x’ρ 及 y’ρ 開始理解,而是從 ∆x 和 ∆y 兩值開始:手續費 ρ = 0.3% 的意思是會從付款中扣掉 0.3 %,也就是從 ∆x 扣。在有手續費的情況下 ∆x 就變成了 (1-ρ)∆x ,若令 γ = 1-ρ 則為 γ∆x。因此,將圖一中的 ∆x 換成 γ∆x,就會得到以下式子:
source: https://www.codecogs.com/latex/eqneditor.php
將等號左方的 γ 移到右方後就得到了圖二中的 ∆x。同理,由於 ∆y 中的 α = ∆x / x ,用 γ∆x 代換 ∆x 就會得到圖二中的 ∆y (有 α 的地方乘上 γ )。而 x’ 還有 y’ 就可以由 ∆x 和 ∆y 推出來了!
然而,將圖二中得到的 x’ 和 y’ 相乘,會得到:
source: https://www.codecogs.com/latex/eqneditor.php
也就是說,當有手續費使得 γ != 1 /ρ != 0,x’ρ * y’ρ 的值其實會稍微和 xy = k 不同:在實作上 γ = 0.997 / ρ = 0.003,因此 1/γ-1 ≒ 0.003。β = ∆y / y 代表的是換得的 Y 幣佔總量的比例,即使最大值為 1,誤差也只有 1 * 0.003,故可知手續費 = 0.3% 對於 k 值的影響極小。
2. 程式碼結構
了解了基本的公式後,就可以開始研究程式碼是怎麼撰寫的。首先來看各個函式的功能:
addLiquidity() 及 removeLiquidity():轉入與轉出資金,留到 三. 流動性 Liquidity 中說明
getInputPrice() 及 getOutputPrice():最主要的函式,用以計算給 ∆x 所能換得的 ∆y 數量、以及為了得到 ∆y 所要支付 ∆x 的數量。此兩函式會被其他負責進行交易、匯幣的函式使用
三組 (eth->Token, Token->eth, Token->Token) 的 swap() 及 transfer():swap() 的收幣人就是付款人、transfer() 的收幣人不是付款人而是指定的對象。基本上這兩函式就是呼叫 getInputPrice() 或是 getOutputPrice() 後進行匯幣的動作,因此不再多做解釋
3. 演算法核心與實作
在研讀程式碼前,先回顧一下 ∆x 和 ∆y 的公式:
首先我們考慮用 ∆x 所能購買到的 ∆y 的 getInputPrice():
什麼…就這幾行程式碼?是的。
以上的程式碼和公式表達方式不同,因此先將 α = ∆x / x 和 β = ∆y / y 代換回來並將上下同乘 x:
source: https://www.codecogs.com/latex/eqneditor.php
由於 γ = 0.003,可以將上下同乘 1000 後得到:
source: https://www.codecogs.com/latex/eqneditor.php
接著就能來對照程式碼了:
(109行) numerator: input_amount 是欲支付的 X 幣數量 ∆x、output_reserve 是 Y 幣數量 y,再乘上 997 後就是等式右邊的上方 (= 997∆xy)
(110行) denominator: input_reserve 是 X 幣的數量,乘上 1000 再加上剛剛算過的 997∆x,就得到了等式右邊的下方 (= 1000x + 997∆x)
此處要注意的是 Vyper 的除法是無條件捨去,等同於 floor() 函式。這會不會造成嚴重的影響呢?如果熟悉 ERC20 的人應該記得,在發幣時輸入的四個參數中有一個參數代表小數點的位數,如同下方程式碼中的 2 代表最後兩位在小數點後。舉例來說,當 getInputPrice() 收到 1234567 為這個幣的 input_amount 時,代表使用者擁有的幣的數目實際上是 12345.67。因此,即使將結果捨去 0.67 後的數字,影響真的不大,況且如果不捨去而選擇無條件進位,那代表交易所反而要虧損一點點啦,太佛心了吧 xD 有興趣者可以看看審計報告的內容,有更詳細地去定義這些誤差所影響的範圍!
再來我們看若要購買 ∆y 需要付出多少 ∆x 的 getOutputPrice()。
一樣先將 α = ∆x / x 、β = ∆y / y 和 γ = 0.003 代換並上下同乘 1000y 得到:
source: https://www.codecogs.com/latex/eqneditor.php
我們已經看過 getInputPrice() 一次了,所以應該能發現第 122–124 行得出的結果和上式相同。要注意的是這邊的結果反而是無條件捨去後直接 +1,因為這是在計算使用者要付多少 ∆x 才能購買到 ∆y,為了不讓交易所虧只能選擇請使用者多付一點點。
4. 段落小結
以上就是撇除匯幣等函示,恆定乘積做市商的 Vyper 實作,沒錯就這樣而已!Uniswap 之所以可以做到低 gas 消耗就是因為這個演算法本身就非常簡單,所需的運算也就是兩三次乘除法而已!
不過我們還沒結束,接下來要談談如何投入資金/注入流動性,而這部分也包含了決定 k 值的精妙機制!
三. 流動性 Liquidity
流動性指的是交易市場中能夠交易的資金/標的物的量。使用自動做市商 (AMM) 而非掛單的最大好處就是市場一定會有流動性,而缺點就是如果交易量越大就會造成越大的滑點 Slippage,意思就是交易價格變動會越大、得到的價格越差 。
source: https://ethresear.ch/t/improving-front-running-resistance-of-x-y-k-market-makers/1281
我們可以用上面提到的 V 文章中的圖片來迅速帶過,畢竟有關注 Uniswap 的讀者大概都已經看過這圖很多次了。
當要兌換的幣的數量越大/占比越重,例如:20% Y 幣的流動性,就會造成要付出比兌換少量時極為不對稱的高額 X 幣。
接著我們要來探討注入流動性的原則,依照市場是否已經有流動性而區分為兩種情形:
1. 第一筆流動性注入、決定 k 值
以下程式碼是 addLiquidity() 函式中 46-48, 51, 及 64-74 行。當市場上還沒有任何流動性時,不會滿足第 51 行而是進入 64 行的 else。
在第 65 行我們可以看到 msg.value ≥ 10¹⁰,以及在 67 行 token_amount 就是其中一個輸入值 max_tokens。這邊代表的是第一個注入流動性的使用者可以自行決定要注入多少 Ether (≥ 10¹⁰) (= x) 以及相應的幣的數量 (= y),也就是上方提到的 k 值 (= x* y),在本例的 X 幣就是 Ether。(本處先不解釋剩餘的程式碼,留到 2. 除了第一筆以外的情況)
那麼問題來了:第一個注入流動性的人要怎麼決定提供各自多少的兩種幣呢?最好的辦法是依照當時兩幣的市價比,讓兩者的價值 (數量 * 價格) 相同,例如:當 1 Ether 的價格為 100 Dai,注入 1 Ether 以及 100 Dai 是最好的,因為兩種幣的總價值是一樣的,以下舉例說明原因。
當 1 Ether 市價為 100 Dai 時,假設第一人決定注入 1 Ether 和 50 Dai (k = 50),總價值為 150 Dai,我們考慮兩種兌換方法:
Ether -> Dai:用 0.1 Ether 來購買 Dai,依照上方公式 (1+0.1)(50-y) = 50 可得 y ≒ 4.55,也就是說得到的價格是 0.1 Ether = 4.55 Dai,遠低於市價 0.1 Ether = 10 Dai,相信沒有人這麼傻~
Dai -> Ether:用 2 Dai 來購買 Ether,依照上方公式 (1-x)(50+2) = 50 可得 x ≒ 0.038,也就是說得到的價格是 2 Dai = 0.038 Ether,高於市價 2 Dai = 0.02 Ether,那麼眼尖的人就會立刻衝來套利了xD
那麼即使如此,第一人有所損失嗎?當然有!假設路人 A 手上有 30 Dai (= 0.3 Ether),A 看到機會後就把 30 Dai 全換成 Ether:(1-x)(50+30) = 50 可得 x = 0.375,大於原本持有的 Dai 的價值 0.3 Ether。此時,第一人即使立刻抽出現存的全部資金 Ether = 0.625 及 Dai = 80,總價值也只剩下 142.5 Dai,比起原本的 150 Dai 還少。以上的計算還有手續費沒有納入考量,但也只有 30 Dai 的 0.3% = 0.09 Dai。
由上例可知,第一位提供流動性的人為了避免自己的損失,確實得依照當時兩幣的市價比去提供相應的數量。傑克,這真是太神奇了0…0
2. 除了第一筆以外的情況
如果市場已經有流動性,使用 addLiquidity() 來注入流動性就會進入第 51 行的 if。
source: https://github.com/Uniswap/uniswap-v1/blob/master/contracts/uniswap_exchange.vy
(53行) eth_reserve: 由於使用者已經透過函式 addLiquidity() 將錢匯入了合約,因此將合約所擁有的 Ether 數量 self.balance (= x + ∆x) 減去使用者匯入的錢 msg.value (= ∆x),得到使用者匯錢之前合約內所擁有的 Ether 數量 (= x)
(54行) token_reserve: self.token 是一個餵入幣地址的 ERC20 instance;透過呼叫 ERC20 的函式 balanceOf() 即可查出合約所擁有的 Y 幣的數量 (= y)
(55行) token_amount: 透過將合約所擁有的 Y 幣的數量 token_reserve (= y) 乘上使用者匯入的錢 msg.value (= ∆x) 對合約原本擁有的Ether 數量 eth_reserve (= x) 的比例,代表使用者應該相應地注入多少 Y 幣 (∆y = y * ∆x / x)。除法一樣是無條件捨去
(56行) liquidity_minted: 將原本交易所中的總流動性 total_liquidity 乘上增加的比率 msg.value / eth_reserve (= ∆x / x) ,代表增加的流動性,隨後會在第 58 行記錄下來
(60行) transferFrom() 函式將使用者應付的 Y 幣數量 token_amount (= ∆y) 匯入當前合約,就完成了流動性的注入。小提示:智能合約中的 assert() 會確保函式內的條件如果失敗就整筆交易 transaction 直接取消,因此只要傳入的參數已經被計算好,於 60 行再進行 transferFrom() 其實與放在前面並沒有太大的差別
以上就是注入流動性的大致實作內容。取出資金 removeLiquidity() 其實與 addLiquidity() 的做法大同小異,因此就不再贅述。
四. 結語
呼,真的累。恆定乘積做市商模型的概念雖然簡單,但解釋起來還是挺複雜的!其實本文並未著墨於審計報告中的主要議題:評估因為整數除法 (不使用浮點數) 而造成的誤差範圍,因為講起來非常複雜、也不是真的這麼需要知道。不過,恰巧就是這些程式碼的細節有可能讓程式產生預期之外的結果!因此,對於有興趣了解該如何去分析智能合約整數除法的讀者,可以研究一下;而 Uniswap 的程式碼因為是用 Vyper 實作,可讀性非常高、同時也不難,因此也非常值得打開來看看、甚至動手實作自己的版本!
最後,如果本文有任何錯誤,請不吝提出,我會盡快做修正;而如果我的文章有幫助到你,可以看看我的其他文章,歡迎一起交流 :)
田少谷 Shao - Medium
Uniswap 解析:恆定乘積做市商模型 Constant Product Market Maker Model 的 Vyper 實作 was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
「浮點數誤差」的推薦目錄:
- 關於浮點數誤差 在 Taipei Ethereum Meetup Facebook 的最佳貼文
- 關於浮點數誤差 在 Taipei Ethereum Meetup Facebook 的最佳解答
- 關於浮點數誤差 在 紀老師程式教學網 Facebook 的最佳貼文
- 關於浮點數誤差 在 Re: [問題] float 精準度觀念問題- 看板C_and_CPP - 批踢踢實業坊 的評價
- 關於浮點數誤差 在 為什麼Float和Double會有誤差(浮點數儲存原理) 的評價
- 關於浮點數誤差 在 D-5 浮點數計算 - YouTube 的評價
- 關於浮點數誤差 在 浮點數的舍入誤差 - 他山教程 的評價
- 關於浮點數誤差 在 JavaScript 浮点数陷阱及解法· Issue #9 · camsong/blog - GitHub 的評價
- 關於浮點數誤差 在 [請益] 如何直接判斷浮點數運算時有誤差(贈P幣) - soft_job 的評價
浮點數誤差 在 Taipei Ethereum Meetup Facebook 的最佳解答
📜 [專欄新文章] The next generation Ethereum Virtual Machine — Ewasm VM
✍️ Peter Lai
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
The next generation Ethereum Virtual Machine — Ewasm VM
The next generation Ethereum Virtual Machine — Crosslink 2019 Taiwan
這篇文章是 Crosslink 2019 Taiwan 的一個議程紀錄:The next generation Ethereum Virtual Machine,由來自 Second State 的工程部 VP Hung-Ying Tai 分享 Ewasm VM 目前研究內容及未來的方向,內容非常精彩,包含了 EVM bytecode 、 Webassembly、Ewasm1.0 以及 Ewasm2.0 。
EVM bytecode 及 Webassembly(WASM)
以太坊的智能合約交易在執行時,例如 :轉 Token 到別的地址,我們是將 EVM bytecode 讀進以太坊的虛擬機執行,而 EVM bytecode 有以下幾點特色:
256 位元且堆疊式(staked-based)的虛擬機
很多高階的指令,例如:SSTORE, SLOAD, SHA3, EC, Call/Create contract
與實體系統架構(通常是 32/64 位元)有差異,而 256 位元則需要靠模擬來完成
較少程式語言(Vyper, Solidity, …)
Webassembly(WASM)是為了讓不同程式語言開發的套件都能在瀏覽器使用的一種二進位程式語言,WASM 有以下幾點特色:
堆疊式(staked-based)的虛擬機:有獨立的區域空間(暫存器或是記憶體),存取堆疊前 3 個物件(EVM 存取 16 個)
支持 32 / 64 位元的操作
沒有高階的指令
RISC 指令集也可以對應到 CPU ISA
較大的社群:主流的瀏覽器都支援,也有較多的程式語言(C++, Rust, GO, …)
Ewasm 1.0
接下來看看以太坊 Ewasm 的特性:
Ewasm 是 wasm 的子集合
因為不能有誤差,所以不支援浮點數運算
只能 import 以太坊的函式庫,避免 importㄒ系統函式庫
在每段指令之前插入 useGAS 來計算 GAS 的花費
Ethereum Environment Interface
EVM 裡有很多像是 SSLOAD, SHA3 的高階指令,這些指令在 Ewasm 1.0 裡,因為 WASM 可以動態讀取函式庫(模組),以太坊定義了 Ethereum Environment Interface 讓客戶端可以用不同的語言實作相對應的函示庫,而且也更容易完成 prototype 跟升級。
下圖是 Ethereum Environment Interface 定義的函數列表。
Ethereum Environment Interface Definition.
如何移除非法的指令?
Ewasm 使用 system contract 移除非法指令以及加入 useGas 的 bytecode,像是浮點數或是非法的 import,有以下兩種做法:
使用 smart contract 檢查合約的 bytecode
像目前的 precompiles 運行在客戶端上,在部署前先檢查合約
下圖是 Ewasm 1.0 的 stack,在部署合約前 Ewasm bytecode 會先經過 Sentinal 的檢查,成功部署後客戶端如果要執行合約會透過 EVM-C 跟 Heru(Wasm Engine)溝通。
Ewasm Stack
效能問題
究竟使用 Ewasm 效能真的會比較快嗎?講者分享各 EVM 執行 Sha1 以及 BN128mul 的結果,可以發現 EVM 在運行 BN128mul 時會是最快,主要是因為 WASM 支持 32 / 64 位元的操作,256 位元則需要另外模擬(1 個 256 位元的運算可以換成 25 個 64 位元的運算),所以 WASM 在跑 BN128mul 時才會比較慢。
Ewasm 2.0
Ewasm 2.0 的智能合約改叫 Execution Environments(EE),與 Ewasm 1.0 不一樣的有下列幾點
EE 全部都是 WASM 寫的
因為支援 cross shard,每個 EE 都是在一個 shard 上執行
EE 只能拿到 state root,而在合約的執行寫法也跟原來不一樣
EE 是 stateless
下圖可以看到 ERC20 Token 在 Ewasm 2.0 跟 Ewasm 1.0 storage 的比較,Ewasm 1.0 每個 data 都會有相對應的 key,而 Ewasm 2.0 只有存 state root,所以只能跟 state root 互動。
Ewasm 2.0 vs Ewasm 1.0
Phase One and Done
目前 Ewasm 2.0 到 phase one and done 的階段,也有測試的網路可以在 shard block 執行 EE,以太坊也有開源 Ewasm 2.0 的測試工具 Scout。
Hello World for Ewasm 2.0
上圖是 Eth 2 的 Hello World EE,可以看到 main 函數裡第一行讀取 pre state root,接下來驗證 block data size 是不是為 0,最後再將 state root 存回去,Eth 2 的智能合約寫起來都會像這樣。
結論
Ewasm 1.0 目前已經支援 EVM 1 大部分的功能也有測試鏈了,second state 開發一個編譯器 soll,能將 solidity 編譯成 Ewasm,想研究的人可以參考看看。
Ewasm 2.0 目前還在研究中,下圖是講者給大家分享的研究及貢獻的方向。
A MAYBE Roadmap
參考
Crosslink 簡報
webassembly.org
scout
soll
Ewasm overview and the precompile problem: Alex Beregszaszi and Casey Detrio @ Ethereum \\ Part 1 — YouTube
Ewasm overview and the precompile problem: Alex Beregszaszi and Casey Detrio @ Ethereum \\ Part 2 — YouTube
Wasm for blockchain&Eth2 execution: Paul Dworzanski,Alex Beregszaszi,Casey Detrio@Ethereum \\ Part 2 — YouTube
Ewasm for sharding
Ewasm updates
Ewasm design
wasm-intro
The next generation Ethereum Virtual Machine — Ewasm VM was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
浮點數誤差 在 紀老師程式教學網 Facebook 的最佳貼文
【Python 線上課程試看搶先體驗 Part1】
📍課程募資倒數1週,55折敬請把握▶︎ https://pse.is/BSPZA
📍真正適合新手的Python課程▶︎計算機概論章節 #已解鎖送給學員!
📍達到350人還會解鎖程式人都該學的『git & github』章節喔!
#影片主題:為什麼在程式裡, 0.1 + 0.2 不等於 0.3?
#重點筆記:
✏️程式語言中無法表示「無限位數」的小數,因此改稱為「浮點數」
✏️浮點數本身其實具有誤差,為什麼呢?
✏️有經驗的工程師,在進行計算時又是如何驗證與除錯的?
這些問題的答案,看影片都有解答喔!
我總是跟同學說:
沒有一套程式教學適合所有人學習,但你可以透過我的教學影片體驗我的教學風格。再決定你要不要跟著紀老師學Python,我能為入門同學做到的是:
1. 注意專有名詞的解說
2. 運用生活例子說明難懂的程式邏輯
3. 課程中你的程式碼問題一定設法幫您解答到您滿意
希望同學能透過我的線上課程效率學習、收穫滿滿
⋯⋯⋯⋯
其他免費教學影片傳送門,這邊走▶︎ https://pse.is/BSPZA
55 折早鳥優惠只到11/28,2019 新技能趁現在搶先投資!
浮點數誤差 在 為什麼Float和Double會有誤差(浮點數儲存原理) 的推薦與評價
一. 浮點數介紹. 一開始先對於float和double做身家調查. 浮點類型的範圍. 類型 · 二. 浮點數產生. 以 float 來說可以儲存4 byte = 32 bit 是說最多可以存32 ... ... <看更多>
浮點數誤差 在 D-5 浮點數計算 - YouTube 的推薦與評價

授課老師單維彰本節說明 浮點數 計算不可避免的 誤差 ,與機器精度的定義。透過課堂實作,聽者將對其概念有所了解。 ... <看更多>
浮點數誤差 在 Re: [問題] float 精準度觀念問題- 看板C_and_CPP - 批踢踢實業坊 的推薦與評價
有另外一個觀念其實對於理解浮點數運算也很有用:
浮點數其實就是有限有效位數的二進位科學記號
既然是科學記號那一些關於其運算的觀念換個底就能套用在浮點數身上
※ 引述《lovejomi (JOMI)》之銘言:
: 我理解為什麼float會有誤差值
: 但是今天朋友討論一件事情
: if (float_var == 1.0f) 這樣寫到底有什麼錯(我認知是 這樣寫 變數的值要完全跟1.0
: 四個byte的memcmp要一樣)
: 1. 在誤差範圍內 (https://en.cppreference.com/w/cpp/types/numeric_limits/epsilo
: n)
: 如果是趨近於1的數字 我這樣判斷會失敗 導致邏輯錯誤? 所以因為這樣條件太嚴苛
: 對於經過運算後的float數值 很可能有一點點誤差產生就不成立了?
: 2. 如果是要完全的相等 , 我能把一個float 一個byte一個byte判斷是否相等來判斷是不
: 是等值嗎?
: 例如
: typedef union
: {
: float value;
: unsigned char bytes[4];
: } IEEE754;
: IEEE754 one;
: one.value = 1.0f;
: IEEE754 target;
: target.value = input;
: 然後memcmp 兩者的bytes
: 還是 float 的== 實作上就是byte compare?
: 3. 浮點數運算出現誤差,可以理解成 當除不盡 或是 除完小數點超過二進位小數 23位
: 無法表示
: 就會產生誤差?
: 4. 因為看不懂std::numeric_limits<T>::epsilon 的那個almost_equal在幹嘛 所以找了
: 一下
: https://stackoverflow.com/a/17341/588477
: 這篇的方法好像是有道理但是請看以下測試
: https://ideone.com/MH6jJW
: 我看VC直接寫
: #define FLT_EPSILON 1.192092896e-07F // smallest such that 1.0+FLT
: _EPSILON != 1.0
: GCC我用gcc -E -dM 去dump (我不知道為什麼找不到定義???怎麼解釋 https://tinyurl.
: com/y8heekq8 )
: #define __FLT_EPSILON__ 1.19209289550781250000e-7F
: 奇怪為什麼會是這樣
: a. stackoverflow的作法錯了?
: b. 為什麼會把差值當成相等?
: c. 到底這個epsilon 最應該用在哪裡呢?
: d. 是不是把almost_equal當成一個正解 才是正確的浮點數比較相等呢?
: 我用以下tool 把 epsilon 看他hex form 反推一下
: 他是2^-23 = 0.00000011920928955078125f乍看之下跟gcc定義一致
: https://www.h-schmidt.net/FloatConverter/IEEE754.html
: 觀念上有些錯誤
: 請大家修正一下
: 謝謝
我們知道在用有限位數的科學記號表示實際值時會進行四捨五入
所以當使用這些經過處理的值運算就有可能產生誤差
十進位科學記號的對應例子例如: 1/3 可能會表示成 3.33333*10^-1
所以當三個這個值相加時會得到的是 9.99999*10^-1, 而不是 1.00000*10^0
為了這個原因我們通常會取一個小範圍, 兩者相減在誤差範圍內的話就視作相等
例如若想要差在 1*10^-4 以內當作相等的話, 上面兩個結果就會當作相等了
到這裡回答了你的 (1) 和 (3)
(2) 其實所有數值型態的 == 都是 byte exact compate
所以才會說直接用 == 比的結果可能不是你要的
(4) 這裡則是又一個科學記號的名詞出現了: ulp
ulp 全名 units in the last place, 直譯叫做「最後一位的單位」
其實指的就是這個科學記號的表示法當中最後一個有效位數上有 1 的數值
以上面我舉例的十進位六位有效數字來說
1.00000*10^0 的最後一個有效位數其值為 1*10^-5, 這就是這個浮點數的 ulp
這個單位通常用來表示誤差大小, 例如上面 1/3 + 1/3 + 1/3 的計算誤差就是 1 ulp
會拿這個當誤差大小的理由看科學記號的表示法很容易理解
那這裡的 epsilon 值其實就是代表某種型態的 1 的 ulp 大小
(這個 epsilon 的定義有個講法是最小的值使其加上 1 之後不等於 1
仔細想想這其實就是在說這個 epsilon 是表示 1 這個浮點數的 ulp 大小)
因此 cppreference 裡的範例就是在說
「x-y 的值 (兩者的誤差) 要小於給定參數個 ulp」
之所以要乘以 x+y 是為了要求得所求數值範圍裡的 ulp 實際值
因為很容易理解 1.00000*10^0 和 4.20000*10^1 和 5.77216*10^-1 的 ulp 都不同
實際上我們需要的 ulp 是多少可以用該值乘上 epsilon 來估計
(參照上一篇回答, 這個 ulp 間隔其實就是上一篇裡提的那些離散格子點)
那麼回到你的問題, 是不是一定要比較浮點數都要用它的 almost_equal 實作?
答案其實是要看你的用途而定
如果你的用途會需要這種類型的誤差估計, 那就需要
它會保證你的數字之間的誤差不超過某個數學上可以掌握的大小
如果只是平常的計算的話, 那其實可以不用這麼嚴格
像我最上面那樣抓一個大致足夠的小值, 相差不到這裡就當它相等就好
這個足夠的小值可以參照這些 epsilon 的實際值來訂
例如 std::numeric_limits<float>::epsilon (FLT_EPSILON)
其值是 2^-23 約是 1.192093 * 10^-7
所以對於 float 可以簡單取一個 10^-5 (寫 1e-5f) 當做平常在用的誤差值
取 100 倍左右的範圍可以適用到稍微大範圍一點的數字
而對於現在比較常用的 double
std::numeric_limits<double>::epsilon (DBL_EPSILON)
其值是 2^-52 約是 2.220446*10^-16, 所以很常看到取 1e-10 ~ 1e-13 來用的寫法
--
有人喜歡邊玩遊戲邊上逼;
也有人喜歡邊聽歌邊打字。
但是,我有個請求,
選字的時候請專心好嗎?
-- 改編自「古 火田 任三郎」之開場白
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 123.195.192.32
※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1537315494.A.002.html
... <看更多>