這是一篇關(guān)于浮點型精度的文章业踢,大致會從三個大的問題入手,去解析精度問題谨履。
字符串轉(zhuǎn)浮點型出現(xiàn)精度丟失
浮點型和浮點型操作精度丟失
NSDecimalNumber如何解決精度丟失
4月21號終于完成了4月份的自考革为,也有些時間來研究一些東西,寫寫博客了糕簿,剛好群里邊有人問關(guān)于浮點型精度的問題探入,我去研究了一些資料,問了一些前輩和同行懂诗,總結(jié)如下新症,結(jié)尾處會有我參考資料的鏈接。(以下內(nèi)容均是基于32位系統(tǒng)進(jìn)行描述的)
首先响禽,我們了解一下字符串的存儲方式徒爹,它是以ASCII碼進(jìn)行存儲的,然后將對應(yīng)的ASCII碼轉(zhuǎn)換成二進(jìn)制存儲在內(nèi)存中芋类。
例如:a對應(yīng)的ASCII碼是97隆嗅,97對應(yīng)的七位二進(jìn)制表示是1100001,八位是01100001侯繁,這兩者區(qū)別可以忽略掉胖喳,八位二進(jìn)制可表示的字符范圍更寬泛而已。因為是一個字符所以只需要1個字節(jié)贮竟,也就是8位丽焊,也就是1組二進(jìn)制。
1.23作為一個字符串它由4個字符組成所以需要4個字節(jié)咕别,也就是32位技健,需要4組二進(jìn)制。它們分別對應(yīng)的ASCII碼是49惰拱、46雌贱、50、51,轉(zhuǎn)換成二進(jìn)制就分別是110001欣孤、101110馋没、110010、110011降传。
浮點型作為一種與整型不同的存儲方式篷朵,它是遵循IEEE754標(biāo)準(zhǔn)的。浮點型在內(nèi)存中的存儲分為了3個部分婆排,分別是1個符號位s声旺,8個指數(shù)E,23個有效數(shù)字M泽论。任意一個二進(jìn)制數(shù)V都可以寫成sM*2^E,s就是正號和負(fù)號卡乾,M就是有效數(shù)字翼悴,E就是指數(shù)。這個公式的意思是將M的小數(shù)點向右移E位幔妨。下面是IEEE754的一些規(guī)定:
1
2
3
4
5
1. 當(dāng)s=0鹦赎,V為正數(shù);當(dāng)s=1误堡,V為負(fù)數(shù)古话。
2. M表示有效數(shù)字,大于等于1锁施,小于2陪踩。
3. 2^E表示指數(shù)位。
4. E的真實值必須再減去一個中間數(shù)悉抵,對于8位的E肩狂,這個中間數(shù)是127。
5. 在計算機(jī)內(nèi)部保存M時姥饰,默認(rèn)這個數(shù)的第一位總是1傻谁,因此可以被舍去,只保存后面的xxxxxx部分列粪。
例如浮點數(shù)10.0审磁,轉(zhuǎn)換成二進(jìn)制是1010,在32位系統(tǒng)下你還要在前面補上28個0岂座,這個二進(jìn)制并不是在內(nèi)存中的存儲樣式态蒂。套用上面的公式你可以寫成+1.010*2^3,其中s是0费什,那么也就是+吃媒,M就是1.01,E就是3。這只是公式赘那,并不是在內(nèi)存中的真正存儲形式刑桑。接下來看下下面的圖,圖中的數(shù)值不用看募舟,跟本文無關(guān)祠斧,主要是看組成的三部分。
看過了上圖拱礁,還是以+1.010*2^3為例琢锋,將其填入該32位的二進(jìn)制中。
首先是符號位S呢灶,也就是圖中的sign吴超,因為是正號,所以sign是0鸯乃;
E是3鲸阻,按第4條規(guī)則,E的真實值需要E+127缨睡,也就是130鸟悴,將其轉(zhuǎn)成二進(jìn)制存儲,也就是10000010奖年;
M是1.010细诸,按上述第5條規(guī)則,舍去1陋守,也就是01000000000000000000000震贵;
所以合在一起,浮點數(shù)10.0在內(nèi)存中的表示就是01000001001000000000000000000000水评。
既然浮點型和字符串在內(nèi)存中的存儲方式都說了屏歹,順道提下整型在內(nèi)存中的存儲方式。
這個很簡單之碗,它只需要考慮一個問題蝙眶,就是正數(shù)和負(fù)數(shù),它由兩部分構(gòu)成褪那,第一個還是符號位幽纷,表示正負(fù),后面31位都是實際存儲的數(shù)字博敬,所以它支持存儲的數(shù)字范圍是-2^31~~~2^31-1友浸。只需要直接將整數(shù)轉(zhuǎn)換成二進(jìn)制就可以了。
比如100偏窝,在內(nèi)存中的二進(jìn)制表示就是00000000000000000000000001100100收恢。
前面說了字符串的存儲方式和浮點型的存儲方式武学,現(xiàn)在這個問題其實挺好解決的了。對了伦意,還要說的一點是火窒,你用的什么方法進(jìn)行強轉(zhuǎn)的,對于NSString類型的轉(zhuǎn)成float驮肉,一般使用的是floatValue方法熏矿,那么可以看下官方文檔對這個方法的解讀。
1
2
3
The floating-point value of the string as a float.
This property doesn’t include whitespace at the beginning of the string. This property is HUGE_VAL or –HUGE_VAL on overflow, 0.0 on underflow. This property is 0.0 if the string doesn’t begin with a valid text representation of a floating-point number.
This method uses formatting information stored in the non-localized value; use an NSScanner object for localized scanning of numeric values from a string.
上文比較有用的信息就是這個方法是通過NSScanner對字符串進(jìn)行逐個掃描离钝,如果不是一個真正的浮點型票编,比如@”abv”這種,這個方法就是0.0卵渴;如果是@”1.23”這種慧域,它就會轉(zhuǎn)化成浮點型1.23。所以可以排除掉這個可能:浮點型和字符串在內(nèi)存中的二進(jìn)制表現(xiàn)形式不同而導(dǎo)致的浪读。
那么昔榴,問題就很清楚了,肯定是浮點型自身存儲成2進(jìn)制的時候發(fā)生了精度丟失瑟啃。這次舉兩個例子對比下:
浮點數(shù)10.0论泛,它的有效數(shù)字M是1.010揩尸,忽略掉整數(shù)位1蛹屿,實際存儲的也就3位是010,在32位情況下岩榆,M最多可以存儲23位有效數(shù)错负,所以它是無損的。
浮點數(shù)1.2勇边,它的有效數(shù)字是0011001100110011001100110011001100110011001100110011犹撒,這個長度大大超過了23位能存儲的,所以它會被截取掉后面超出的部分粒褒,超出部分明顯不全是0识颊,所以會對它的精度造成損失。
總結(jié):精度損失不損失需要看十進(jìn)制的數(shù)據(jù)能否精確的轉(zhuǎn)換為二進(jìn)制奕坟。
關(guān)于這個問題祥款,其實看懂了上面的內(nèi)容,就知道這個問題出在哪里了月杉。比如a=b+c;首先浮點型b和c自身存儲就已經(jīng)損失精度了刃跛,其次得到的結(jié)果a如果也是一個不能精確轉(zhuǎn)為二進(jìn)制的浮點型,那么必然造成精度的2次缺失苛萎,會跟你想象中的結(jié)果差距更大桨昙。
找到了問題的產(chǎn)生原因检号,再來說說iOS提供的NSDecimalNumber這個類如何解決這個問題。我們先來看下系統(tǒng)文檔:
1NSDecimalNumber, an immutable subclass of NSNumber, provides an object-oriented wrapper for doing base-10 arithmetic. An instance can represent any number that can be expressed as mantissa x 10^exponent where mantissa is a decimal integer up to 38 digits long, and exponent is an integer from –128 through 127.
上述大概意思是NSDecimalNumber是NSNumber的一個子類蛙酪,它提供了一個基于10進(jìn)制面向?qū)ο蟮姆庋b齐苛。它也有一個類似IEEE754的公式:N*M*10^E
例如浮點型1.23,套用上面公式就是1*123*10^(-2)滤否。它會將這個浮點型包裝成一個NSDecimalNumber對象脸狸,N代表是正數(shù)還是負(fù)數(shù),在本例中是1藐俺,M代表將浮點型轉(zhuǎn)化為整數(shù)后的數(shù)炊甲,在本例中是123,E代表指數(shù)欲芹,在本例中是-2卿啡。
這樣來看,系統(tǒng)的處理方式其實很明顯了菱父,這個類不存儲浮點型颈娜,只存儲整數(shù),自然避免了IEEE754那種方式的精度損失浙宜,當(dāng)然類在內(nèi)存中肯定跟浮點型是不同的官辽,但是M這個屬性保存的是整數(shù)型,是同浮點型一樣都是基礎(chǔ)數(shù)據(jù)類型粟瞬。
其實還有一個整型強轉(zhuǎn)浮點型的問題同仆,這個問題跟字符串轉(zhuǎn)浮點型的情況還是不同的,這個是因為兩者對二進(jìn)制的轉(zhuǎn)化方式不同裙品。整型存儲在內(nèi)存中是直接轉(zhuǎn)化成二進(jìn)制的俗批,浮點型雖然也是轉(zhuǎn)成二進(jìn)制存儲,但它需要符合IEEE754標(biāo)準(zhǔn)市怎,所以你直接把整型的二進(jìn)制取出來當(dāng)浮點型使用岁忘,肯定會出現(xiàn)對應(yīng)的問題。