可以存储时间戳的最小字节数是多少?

我想在C中创建自己的时间戳数据结构。

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

可能的最小数据结构是什么?

好吧,你可以把它全部打包成一个unsigned short (这是2个字节 ,5位为Day,5位为小时,6位为分钟)…并使用一些移位和屏蔽来获取值。

 unsigned short timestamp = ; // 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; 

但你真的需要这个吗? 标准时间function不足够吗? 位域根据架构,填充等而有所不同……不是便携式构造。

为什么不使用C time()函数的(4字节?)输出,并将NULL作为参数。 这只是Unix纪元时间(即自1970年1月1日以来的秒数)。 就像Joe的回答一样,它给你提供更多的成长空间,而不是任何试图在几个月,几天和几年内打包的答案。 这是标准的。 在标准C中(至少在Unix上)将time_t变量转换为实际时间是微不足道的,并且大多数情况下,如果您有一个旨在保存3字节变量的数据结构,则无论如何它都可以舍入到4个字节。

我知道你正在努力优化大小,但4个字节非常小。 即使你截断了顶部字节,你仍然可以获得194天的不同时间。

你可以通过花time(NULL)并将其除以60来存储它,将其截断一分钟并存储它来获得更多。 如上所示,3个字节为您提供388个月,而对于2个字节,您可以存储45天。

我会选择4字节版本,因为我没有看到2,3和4字节之间的区别对任何运行或不运行的程序都是重要的或至关重要(除非它是一个引导加载程序)。 获得更简单,操作更简单,最终可能会让您免受许多麻烦。

编辑:我发布的代码不起作用。 我已经有3个小时的睡眠时间,我会弄清楚如何正确地进行正确的训练。 在此之前,您可以自己实现。

注意:原始问题已经过编辑,不再需要月份。 原始计算如下:

这只是你想要做多少计算的问题。 打包它的最简单的方法是你可以创建自己的类型,并使用以下数学转换和相应的整数:

有效范围是:

月:1-12  - >(0-11)+1
日:1-31  - >(0-30)+1
小时:0-24
分钟:0-60

您可以选择存储值的订单(我将按上述顺序保留)。

第1天 -  1小时分钟
 (0-11)(0-30)(0-23)(0-59) 

使用以下公式作为指导,进行一些乘法/除法以转换值:

 value =(((月 -  1)* 31 +(日 -  1))* 24 +小时)* 60 +分钟

因此,您具有最小值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位)并使用简单的数学运算符来正常使用它。

但是那里有一些空间可以玩,所以让我们看看我们是否可以让这更容易:

有效范围是:

月:1-12  - >(0-11)+1 --- 4位(你甚至不需要-1)
日:1-31  - >(0-30)+1 --- 5位(你再一次不需要-1) 
小时:0-24 --- 5位
分钟:0-60 --- 6位

这总共是20位,而且很容易操作。 因此,除了使用简单的位移之外,你不会通过压缩得到任何东西,你可以像这样存储值:

 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 ---月--- ----天------- ---小时---  - 分钟---

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

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

让你的范围为0到44,639,它可以整齐地放在16位short

虽然有一些空间可以玩,所以让我们看看我们是否可以让这更容易:

有效范围是:

日:1-31  - >(0-30)+1 --- 5位(你甚至不需要-1) 
小时:0-24 --- 5位
分钟:0-60 --- 6位

总共16位,再次真正易于操作。 所以….存储这样的值:

 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
 ----天------- ---小时---  - 分钟---

对于您描述的用例,(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(),除以eig,然后向上舍入。

    log2(60 * 60 * 24 * 31 * 12)= 24.9379位= 4个字节

  • 您可以通过组合非exception字段(例如,存储一年中的某一天而不是月份和日期)来节省一些额外的空间,但它很少值得。

    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  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表示基本相同的事情。 这将获得最小的表示,小于按位交错表示。