最近發(fā)現(xiàn)在iOS中將String類型轉(zhuǎn)化為Double類型的時候會有莫名奇妙的精度丟失問題累魔,甚至在Double與Double之間的乘除運(yùn)算結(jié)果也會出現(xiàn)很奇葩的精度問題屎飘。試過Objective-C和Swift,都有同樣的問題个粱,看來是iOS系統(tǒng)底層對這些情況有特殊處理吧古毛。
下面以一個很常見的場景來說明問題:
假如在你的項(xiàng)目中需要提供給用戶“提現(xiàn)”的功能,并收取一定比例的手續(xù)費(fèi)(相信玩過各大比特幣交易所的同學(xué)深有感觸)。要求金額保留兩位小數(shù)稻薇,并以手續(xù)費(fèi)四舍五入為準(zhǔn)嫂冻,計(jì)算實(shí)際提取金額。
創(chuàng)建很簡單的視圖:
我們輸入提現(xiàn)金額塞椎,并實(shí)時計(jì)算手續(xù)費(fèi)桨仿,那么我們可能會這樣寫:
得到的結(jié)果是:
發(fā)現(xiàn)問題了嗎?
按道理案狠,我們輸入1234.1的時候服傍,手續(xù)費(fèi)四舍五入保留兩位小數(shù)應(yīng)該是61.71,但這里結(jié)果是61.70骂铁,而且實(shí)際提取的金額和手續(xù)費(fèi)的金額加起來也不等于1234.1吹零。
看圖中的斷點(diǎn),是不是很驚訝拉庵!為什么inputValue的值是1234.0999999灿椅?這就是題目中說的惡心的精度。48行直接將String類型轉(zhuǎn)Double類型就會出現(xiàn)這樣奇怪的問題钞支,然后計(jì)算的feeValue就相應(yīng)的變小了一點(diǎn)點(diǎn)茫蛹,再四舍五入就少了0.01了,同樣地烁挟,第55行的結(jié)果是1172.39499婴洼,四舍五入之后變成了1172.39。所以撼嗓,總的來說窃蹋,手續(xù)費(fèi)不對,實(shí)際提取的也不對了静稻。
怎么辦呢? 有兩種方法: 一是手動修正手續(xù)費(fèi)的值匈辱;二是使用NSDecimalNumber
振湾,也是最推薦的方式。
首先第一種方法亡脸,既然我們知道系統(tǒng)會丟失一點(diǎn)精度押搪,那我們在轉(zhuǎn)化之后手動修復(fù)一點(diǎn)點(diǎn),比如給inputValue加上一個很小的小數(shù)浅碾,然后再對feeValue手動四舍五入大州,這樣就保證了feeValue的值是正確的,也能保證realValue四舍五入的結(jié)果是對的:
第二種方法是推薦使用的垂谢,在處理金錢相關(guān)的精度問題首先應(yīng)該考慮使用它
其中我們所有的操作都是基于String和NSDecimalNumber的厦画,中間不涉及Double的轉(zhuǎn)換。其中NSDecimalNumberHandler
是可以定義一些行為參數(shù)的:
scale : 需要保留的精度。
raiseOnExactness : 為YES時在處理精確時如果有錯誤根暑,就會拋出異常力试。
raiseOnOverflow : YES時在計(jì)算精度向上溢出時會拋出異常,否則返回排嫌。
raiseOnUnderflow : YES時在計(jì)算精度向下溢出時會拋出異常畸裳,否則返回.
raiseOnDivideByZero : YES時。當(dāng)除以0時會拋出異常淳地,否則返回怖糊。
這樣得到的結(jié)果是精確的NSDecimalNumber
類型的,我么可以像第56行那樣取出他的string格式結(jié)果颇象。
使用這兩種方式的結(jié)果如下: