問題現(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)化為日期類型
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
問題原因
時(shí)間戳(精確到毫秒)使用long long 格式存儲(chǔ)土居,2020年目前的時(shí)間戳位數(shù)是13位,long long絕對(duì)足夠存儲(chǔ)下來嬉探,由于是整數(shù)類型擦耀,精度也肯定不會(huì)丟失。
但是當(dāng)將該時(shí)間戳轉(zhuǎn)化為NSDate類型時(shí)涩堤,精度缺丟失了眷蜓,問題肯定出現(xiàn)在下面這行代碼中:
NSDate *convertDate = [NSDate dateWithTimeIntervalSince1970:(timeInterval/1000.f)];
這行代碼中,存在一個(gè)隱式類型轉(zhuǎn)換胎围,long long / float 類型吁系,默認(rèn)得到的是float類型,float類型存不下一個(gè)時(shí)間戳嗎白魂?答案是肯定能存下汽纤,但是精度沒保障。
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ù)都是無法精確保證。
實(shí)際測(cè)試背传,13位時(shí)間戳(毫秒)
1591760542219
移除1000.0得到的浮點(diǎn)數(shù)時(shí)間戳(單位是秒)
1591760512.000000
對(duì)比已經(jīng)出現(xiàn)了精度丟失呆瞻,丟失的范圍在100秒以內(nèi),這也是上面問題現(xiàn)象中時(shí)間出現(xiàn)偏差的原因径玖;
問題解法
- 本地使用double存儲(chǔ)服務(wù)端給的時(shí)間戳
- 將代碼中的隱式轉(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é)
浮點(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)化得說明瞳购,我們假設(shè)其指數(shù)和尾數(shù)都是以原碼(正數(shù)符號(hào)位為0,負(fù)數(shù)符號(hào)位為1亏推,其中指數(shù)部分也包含一個(gè)符號(hào)位)的方式來表示学赛,簡(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)
通過上面的示例,我們可以知道童擎,決定float表示范圍的是指數(shù)滴劲,決定float表示精度的是尾數(shù);
再來粗略得計(jì)算一下float的表示范圍:
8bit 指數(shù)顾复,可以表示的指數(shù)為 -126~+127(0和255有其它用戶班挖,這里不展開講)則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,也就是說尾數(shù)數(shù)值超過這個(gè)值芯砸,float將無法精確表示萧芙,所以float最多能表示小數(shù)點(diǎn)后7位,但絕對(duì)能保證的為6位(這里指科學(xué)計(jì)數(shù)法的方式)假丧,用普通十進(jìn)制標(biāo)識(shí)一下340282346638528859811704183484516925440.000000 后面的數(shù)值范圍都是無法精確表示的双揪。
再看一眼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í)間戳卻無能為力婴噩,iOS系統(tǒng)存儲(chǔ)的時(shí)間戳也是默認(rèn)使用的double類型擎场,所以自己處理時(shí)間戳格式類型轉(zhuǎn)換時(shí)羽德,需要注意float的表示精度問題。
參考文獻(xiàn):IEEE754 浮點(diǎn)數(shù)的表示方法