iOS 時(shí)間戳精度丟失問(wèn)題(float=longlong/float)

問(wèn)題現(xiàn)象

使用long long 格式保持服務(wù)端返回的時(shí)間戳,然后本地展示該時(shí)間戳?xí)r,發(fā)現(xiàn)總是差一兩分鐘

    NSDate *currentDate = [NSDate date];
    //模擬獲得服務(wù)器傳回的單位是毫秒的時(shí)間戳
    long long timeInterval = [currentDate timeIntervalSince1970] * 1000;
    //毫秒轉(zhuǎn)化為秒,再轉(zhuǎn)化為日期類(lèi)型
    NSDate *convertDate = [NSDate dateWithTimeIntervalSince1970:(timeInterval/1000.f)];
    NSLog(@"currentDate:%@,convertDate:%@",currentDate,convertDate);

輸出效果: 這里注意分鐘和秒已經(jīng)出現(xiàn)了誤差(實(shí)際是精度丟失)
currentDate:Wed Jun 10 11:17:34 2020,
convertDate:Wed Jun 10 11:18:24 2020

問(wèn)題原因

時(shí)間戳(精確到毫秒)使用long long 格式存儲(chǔ),2020年目前的時(shí)間戳位數(shù)是13位,long long絕對(duì)足夠存儲(chǔ)下來(lái),由于是整數(shù)類(lèi)型,精度也肯定不會(huì)丟失。
但是當(dāng)將該時(shí)間戳轉(zhuǎn)化為NSDate類(lèi)型時(shí),精度缺丟失了,問(wèn)題肯定出現(xiàn)在下面這行代碼中:

NSDate *convertDate = [NSDate dateWithTimeIntervalSince1970:(timeInterval/1000.f)];

這行代碼中,存在一個(gè)隱式類(lèi)型轉(zhuǎn)換,long long / float 類(lèi)型,默認(rèn)得到的是float類(lèi)型,float類(lèi)型存不下一個(gè)時(shí)間戳嗎?答案是肯定能存下,但是精度沒(méi)保障。

float的十進(jìn)制精度是小數(shù)點(diǎn)后6~7位,能絕對(duì)保證的只有6位。
粗略得看一下這個(gè)13位的時(shí)間戳1591760542219,轉(zhuǎn)化為浮點(diǎn)數(shù)的十進(jìn)制1.1591760542219 * 10^13,即1.159176之后的小數(shù)都是無(wú)法精確保證。
實(shí)際測(cè)試,13位時(shí)間戳(毫秒)
1591760542219
移除1000.0得到的浮點(diǎn)數(shù)時(shí)間戳(單位是秒)
1591760512.000000
對(duì)比已經(jīng)出現(xiàn)了精度丟失,丟失的范圍在100秒以?xún)?nèi),這也是上面問(wèn)題現(xiàn)象中時(shí)間出現(xiàn)偏差的原因;

問(wèn)題解法

  1. 本地使用double存儲(chǔ)服務(wù)端給的時(shí)間戳
  2. 將代碼中的隱式轉(zhuǎn)換去掉,強(qiáng)制將long long 格式的時(shí)間戳轉(zhuǎn)為double后再除以1000.0

根本原因:float的表示精度很低

這就要從浮點(diǎn)數(shù)的表示法講起了IEEE754 浮點(diǎn)數(shù)的表示方法

浮點(diǎn)數(shù)表示法的簡(jiǎn)要總結(jié)

float.jpeg

浮點(diǎn)數(shù)在計(jì)算機(jī)中的存儲(chǔ)格式如上圖所示,由符號(hào)位,指數(shù),尾數(shù)三部分構(gòu)成,float和double的區(qū)別僅體現(xiàn)在指數(shù)和尾數(shù)所占位數(shù)不同。
以float為例,符號(hào)位1bit,指數(shù)占8bit,尾數(shù)23bit。為了直觀簡(jiǎn)化得說(shuō)明,我們假設(shè)其指數(shù)和尾數(shù)都是以原碼(正數(shù)符號(hào)位為0,負(fù)數(shù)符號(hào)位為1,其中指數(shù)部分也包含一個(gè)符號(hào)位)的方式來(lái)表示,簡(jiǎn)化的示例:

0.75 = 0100 0000 1100 0000 0000 0000 0000 0000

符號(hào)位 0 表示為正;指數(shù)位1000 0001表示為-1 尾數(shù)100 …… (此處省略20個(gè)0)表示1.1(二進(jìn)制是1.1,十進(jìn)制就是1.5)(由于尾數(shù)使用“二進(jìn)制的科學(xué)計(jì)數(shù)法”,所以首位的整數(shù)部分的1默認(rèn)存在,不占實(shí)際的空間,僅存儲(chǔ)小數(shù)點(diǎn)后面尾數(shù),這也是為什么取名為尾數(shù)的原因吧) 所以

0.75 = 1.5 * 2^(-1)

通過(guò)上面的示例,我們可以知道,決定float表示范圍的是指數(shù),決定float表示精度的是尾數(shù);
再來(lái)粗略得計(jì)算一下float的表示范圍
8bit 指數(shù),可以表示的指數(shù)為 -126~+127(0和255有其它用戶(hù),這里不展開(kāi)講)則float能表示的最大值為
floatMax = +(1.11111111111111111111111) × 2 ^127 ≈ 3.402823 e +38
這里用10進(jìn)制直觀的展示一下340282346638528859811704183484516925440.000000,整數(shù)位有39位
對(duì)應(yīng)的float能表示的最小值為
floatMin = -(1.11111111111111111111111) × 2 ^(-126) ≈ ?1.175494e ?38
float能表示的精度由23bit的尾數(shù)決定,其最大值為2^23-1 = 8388607,也就是說(shuō)尾數(shù)數(shù)值超過(guò)這個(gè)值,float將無(wú)法精確表示,所以float最多能表示小數(shù)點(diǎn)后7位,但絕對(duì)能保證的為6位(這里指科學(xué)計(jì)數(shù)法的方式),用普通十進(jìn)制標(biāo)識(shí)一下340282346638528859811704183484516925440.000000 后面的數(shù)值范圍都是無(wú)法精確表示的。
再看一眼double的表示精度:尾數(shù)域?yàn)?2位,最大值2^52?1=4,503,599,627,370,495 所以雙精度浮點(diǎn)數(shù)的十進(jìn)制的精度最高為 16 位,絕對(duì)保證的為15位,

所以float能表示的數(shù)值范圍很大(+-10^38),但是對(duì)于一個(gè)精確到毫秒的只有13位有效數(shù)值的時(shí)間戳卻無(wú)能為力,iOS系統(tǒng)存儲(chǔ)的時(shí)間戳也是默認(rèn)使用的double類(lèi)型,所以自己處理時(shí)間戳格式類(lèi)型轉(zhuǎn)換時(shí),需要注意float的表示精度問(wèn)題。

參考文獻(xiàn):IEEE754 浮點(diǎn)數(shù)的表示方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容