簡體   English   中英

可以存儲時間戳的最小字節數是多少?

[英]What is the smallest number of bytes that can store a timestamp?

我想在C中創建自己的時間戳數據結構。

DAY(0 - 31),HOUR(0 - 23),MINUTE(0 - 59)

可能的最小數據結構是什么?

好吧,你可以把它全部打包成一個unsigned short (這是2個字節 ,5位為Day,5位為小時,6位為分鍾)......並使用一些移位和屏蔽來獲取值。

unsigned short timestamp = <some value>; // Bits: DDDDDHHHHHMMMMMM

int day = (timestamp >> 11) & 0x1F;
int hour = (timestamp >> 6) & 0x1F;
int min = (timestamp) & 0x3F;

unsigned short dup_timestamp = (short)((day << 11) | (hour << 6) | min); 

或使用宏

#define DAY(x)    (((x) >> 11) & 0x1F)
#define HOUR(x)   (((x) >> 6)  & 0x1F)
#define MINUTE(x) ((x)         & 0x3F)
#define TIMESTAMP(d, h, m) ((((d) & 0x1F) << 11) | (((h) & 0x1F) << 6) | ((m) & 0x3F)

(你在當前版本的問題中沒有提到月/年,所以我省略了它們)。

[ 編輯 :使用unsigned short - not signed short 。]

你的意思是0-23和分鍾0-59? 我聽說過閏秒但不是閏秒或小時。

(log (* 31 60 24) 2)
=> 15.446

因此,您可以將這些值擬合為16位或2個字節。 這是一個好主意是一個完全不同的問題。

  • 月份:范圍1 - 12 => 4位
  • 日期:范圍1 - 31 => 5位
  • 小時:范圍0 - 24 => 5位
  • 分鍾:范圍0 - 60 => 6位

  • 總計:20位

您可以使用位域並使用編譯器/平台特定的pragma來保持緊密:

typedef struct packed_time_t {
    unsigned int month  : 4;
    unsigned int date   : 5;
    unsigned int hour   : 5;
    unsigned int minute : 6;
} packed_time_t; 

但你真的需要這個嗎? 標准時間功能不足夠嗎? 位域根據架構,填充等而有所不同......不是便攜式構造。

注意:原始問題已被編輯,不再需要月份。 原始計算如下:

這只是你想要做多少計算的問題。 打包它的最簡單方法是,如果你可以創建自己的類型,並使用以下數學轉換和相應的整數:

有效范圍是:

Month: 1-12 -> (0-11)+1
Day: 1-31 -> (0-30)+1
Hour: 0-24
Minute: 0-60

您可以選擇存儲值的訂單(我將按上述順序保存)。

Month-1 Day-1  Hour   Minute
(0-11)  (0-30) (0-23) (0-59)

使用以下公式作為指導,進行一些乘法/除法以轉換值:

value = (((Month - 1) * 31 + (Day - 1)) * 24 + Hour) * 60 + Minute

因此,您具有最小值0和最大值((11*31+30)*24+23)*60+59 ,即535,679。 因此,您需要最少20位才能將此值存儲為無符號整數( 2^20-1 = 1,048,575; 2^19-1 = 524,287 )。

如果你想讓事情變得困難但是保存一個字節,你可以使用3個字節並自己操作它們。 或者您可以使用int(32位)並使用簡單的數學運算符來正常使用它。

但是那里有一些空間可以玩,所以讓我們看看我們是否可以讓這更容易:

有效范圍是:

Month: 1-12 -> (0-11)+1 --- 4 bits (you don't even need the -1)
Day: 1-31 -> (0-30)+1   --- 5 bits (you again don't need the -1) 
Hour: 0-24              --- 5 bits
Minute: 0-60            --- 6 bits

總共20位,真的很容易操作。 因此,除了使用簡單的位移之外,你不會通過壓縮得到任何東西,你可以像這樣存儲值:

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
---Month--- ----Day------- ---Hour--- --Minute---

如果你不關心月份,你可以得到的最緊的是:

value = ((Day - 1) * 24 + Hour) * 60 + Minute

讓你的范圍從0到44,639,可以整齊地放在16位short

雖然有一些空間可以玩,所以讓我們看看我們是否可以讓這更容易:

有效范圍是:

Day: 1-31 -> (0-30)+1 --- 5 bits (you don't even need the -1) 
Hour: 0-24            --- 5 bits
Minute: 0-60          --- 6 bits

這總共是16位,並且再次非常容易操作。 所以....存儲這樣的值:

15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
----Day------- ---Hour--- --Minute---

為什么不使用C time()函數的(4字節?)輸出,並將NULL作為參數。 它只是Unix紀元時間(即自1970年1月1日以來的秒數)。 就像Joe的回答一樣,它給你提供比任何試圖以幾個月,幾天和幾年打包的答案更多的成長空間。 這是標准的。 在標准C中(至少在Unix上)將time_t變量轉換為實際時間是微不足道的,並且大多數情況下,如果您有一個旨在保存3字節變量的數據結構,則無論如何它都可以舍入到4個字節。

我知道你正在努力優化大小,但4個字節非常小。 即使你截斷了頂部字節,你仍然可以獲得194天的不同時間。

你可以通過從time(NULL)取出time(NULL)並將其除以60然后再存儲它,將其截斷一分鍾並存儲它來獲得更多。 如上所示,3個字節為您提供388個月,而對於2個字節,您可以存儲45天。

我會選擇4字節版本,因為我沒有看到2,3和4字節之間的區別對於任何運行或不運行的程序都是重要的或至關重要(除非它是一個引導加載程序)。 獲得更簡單,操作更簡單,最終可能會為您節省許多麻煩。

編輯:我發布的代碼不起作用。 我已經有3個小時的睡眠時間,我會弄清楚如何正確地進行正確的比賽。 在此之前,您可以自己實現。

對於您描述的用例,(31天范圍內的分鍾分辨率)我只使用16位分鍾計數器。 如果要序列化此數據(到磁盤,網絡),則可以使用一些可變長度整數編碼來保存小值的字節。

通常,您可以按如下方式計算此答案(其中log2是基數2對數,即位數):

  • 如果你想使用移位和掩碼來獲取和輸出數據,請記錄每個字段的可能值數量的log2(),向上舍入(得到位),添加結果(得到總位),除以8(總字節數,w。小數字節),再次向上舍入(總字節數)。

    log2(60)+ log2(60)+ log2(24)+ log2(31)+ log2(12)= 6 + 6 + 5 + 5 + 4 = 26位= 4字節

  • 如果你想通過乘以&加/除&模數來輸入和輸出字段,將每個字段的可能值的數量相乘並取log2(),除以eigth,然后向上舍入。

    log2(60 * 60 * 24 * 31 * 12)= 24.9379位= 4個字節

  • 您可以通過組合非異常字段(例如,存儲一年中的某一天而不是月份和日期)來節省一些額外的空間,但它很少值得。

    log2(60 * 60 * 24 * 366)= 24.91444位= 4個字節

- MarkusQ“教人釣魚”

只是提供一個替代方案:

  • 如果你只需要分鍾級別的分辨率,
  • 並且你沒有跨越日期邊界(月/年)
  • 並且您的消息是有序的,保證交付

然后,您可以將時間戳存儲為距離最后一條消息的時間戳的偏移量。

在這種情況下,您只需要足夠的位來保持消息之間的最大分鍾數。 例如,如果相隔最多255分鍾發出消息,那么一個字節就足夠了。

但是,請注意,第一條消息可能需要在其有效負載中包含絕對時間戳,以進行同步。

[我不是說這是一個很好的解決方案 - 它相當脆弱並且做了很多假設 - 只是另一種假設]

60分鍾/小時意味着您需要至少6位來存儲分鍾(自第59分鍾== 111011b),而24小時/天意味着另外5位(第23小時== 10111b)。 如果你想要考慮任何(可能的)366天/年,你需要9位(第366天(第1天365 = = 0)== 101101101b)。 因此,如果您想以純粹可訪問的格式存儲所有內容,則需要20位== 3字節。 或者,添加Month字段會使總可能的Days值從366變為31 - 減少到5位,當月還有4位。 這也可以給你20位,或3位字節,4位備用。

相反,如果您從某個開始日期開始跟蹤日期只需幾分鍾,那么在您再次轉為0之前,3個字節將為您提供16,777,215分鍾的分辨率 - 大約為279,620小時,11,650天和大約388個月,以及這是使用所有24位。 這可能是一個更好的方法,如果你不關心秒,如果你不介意花一點點執行時間來解釋小時,日和月。 這將是更容易增加!

當天的5位加上小時的5位加上分鍾的6位等於無符號短路。 任何進一步的打包都不會減少所需的存儲空間,並且會增加代碼復雜性和CPU使用率。

好吧,無論多余的HOUR 24和MINUTE 60,我們有31 x 24 x 60 = 44,640個可能的獨特時間值。 2 ^ 15 = 32,768 <44,640 <65,536 = 2 ^ 16所以我們需要至少16位(2字節)來表示這些值。

如果我們不想每次都使用模運算來訪問值,我們需要確保將每個存儲在它自己的位字段中。 我們需要5位來存儲DAY,5位來存儲HOUR,以及6位來存儲MINUTE,它仍然適合2個字節:

struct day_hour_minute {
  unsigned char DAY:5; 
  unsigned char HOUR:5;
  unsigned char MINUTE:6;
};

包括MONTH會使我們的獨特時間值增加12倍,得到535,680個唯一值,這需要至少20位才能存儲(2 ^ 19 = 524,288 <535,680 <1,048,576 = 2 ^ 20),這需要至少3個字節。

同樣,為了避免模運算,我們需要一個單獨的MONTH位域,它應該只需要4位:

struct month_day_hour_minute {
  unsigned char MONTH:4;
  unsigned char DAY:5;
  unsigned char HOUR:5;
  unsigned char MINUTE:6;
  unsigned char unused: 4;
};

但是,在這兩個示例中,請注意C更喜歡其數據結構是切入的 - 也就是說,它們是4或8個字節(通常)的倍數,因此它可以填充您的數據結構,超出最低限度的要求。

例如,在我的機器上,

#include <stdio.h>

struct day_hour_minute {
  unsigned int DAY:5;
  unsigned int HOUR:5;
  unsigned int MINUTE:6;
};
struct month_day_hour_minute {
  unsigned int MONTH:4;
  unsigned int DAY:5;
  unsigned int HOUR:5;
  unsigned int MINUTE:6;
  unsigned int unused: 4;
};

#define DI( i ) printf( #i " = %d\n", i )
int main(void) {
  DI( sizeof(struct day_hour_minute) );
  DI( sizeof(struct month_day_hour_minute) );
  return 0;
}

打印:

sizeof(struct day_hour_minute) = 4
sizeof(struct month_day_hour_minute) = 4

為了簡化這一點而不失一般性,

日(0 - 30),小時(0 - 23),分鍾(0 - 59)

encoding = Day + (Hour + (Minute)*24)*31

Day = encoding %31
Hour = (encoding / 31) % 24
Minute = (encoding / 31) / 24

編碼的最大值是44639,略小於16位。

編輯:rampion表示基本相同的事情。 這會使您獲得最小的表示,這比按位交錯表示要少。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM