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

时间:2009-02-27 18:02:57

标签: c timestamp

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

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

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

12 个答案:

答案 0 :(得分:17)

好吧,你可以将所有内容打包成unsigned short 2个字节,每天5位,小时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 - 未签名short。]

答案 1 :(得分:7)

你的意思是0-23和分钟0-59?我听说过闰秒但不是闰秒或小时。

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

因此,您可以将这些值拟合为16位或2个字节。这是一个好主意是一个完全不同的问题。

答案 2 :(得分:5)

  • 月份:范围1 - 12 =&gt; 4位
  • 日期:范围1 - 31 =&gt; 5位
  • 小时:范围0 - 24 =&gt; 5位
  • 分钟:范围0 - 60 =&gt; 6位

  • 总计:20位

您可以使用位域并使用编译器/平台特定的编译指示来保持紧密:

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

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

答案 3 :(得分:4)

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

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

有效范围是:

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---

答案 4 :(得分:4)

为什么不使用带有time()的C NULL函数的(4字节?)输出作为参数。这只是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个小时的睡眠时间,我会弄清楚如何正确地进行正确的比赛。在此之前,您可以自己实现。

答案 5 :(得分:2)

对于您描述的用例,(31天范围内的分钟分辨率)我只使用16位分钟计数器。如果要序列化此数据(到磁盘,网络),则可以使用一些可变长度整数编码来保存小值的字节。

答案 6 :(得分:2)

通常,您可以按如下方式计算此答案(其中log2是基数2的对数,即位数):

  • 如果要使用移位和掩码来获取和输出数据,请记录每个字段的可能值数量的log2(),向上舍入(获取位),添加结果(以获取总比特),除以8(总字节数,w。小数字节),再次向上舍入(总字节数)。

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

  • 如果您希望通过乘以&amp;来获取字段。添加/分割&amp; modulo,将每个字段的可能值的数量相乘,并取log2(),除以eig,然后向上舍入。

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

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

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

- MarkusQ“教人钓鱼”

答案 7 :(得分:1)

只是提供另一种选择:

  • 如果您只需要分钟级别的分辨率,
  • 并且您没有跨越日期边界(月/年)
  • 并且您的邮件是有序且保证投放

然后您可以将时间戳存储为距离最后一条消息的时间戳的偏移量。

在这种情况下,您只需要足够的位来保持消息之间的最大分钟数。例如,如果相隔最多255分钟发出消息,那么一个字节就足够了。

但请注意,第一条消息可能需要在其有效负载中包含绝对时间戳,以便进行同步。

[我不是说这是一个很好的解决方案 - 它相当脆弱并且做了很多假设 - 只是另一种假设]

答案 8 :(得分:1)

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位备用。

相反,如果您从某个开始日期开始跟踪日期只需几分钟,那么3个字节会在您再次转为0之前给出分辨率为16,777,215分钟 - 这大约是279,620小时,11,650天和大约388几个月,这是使用全部24位。这可能是一个更好的方法,如果你不关心秒,如果你不介意花一点点执行时间来解释小时,日和月。 这会更容易增加!

答案 9 :(得分:0)

当天加5位 小时加5位 分钟的6位等于无符号短路。任何进一步的打包都不会减少所需的存储空间,并且会增加代码复杂性和CPU使用率。

答案 10 :(得分:0)

好吧,不管多余的HOUR 24和MINUTE 60,我们有31 x 24 x 60 = 44,640个可能的唯一时间值。 2 ^ 15 = 32,768&lt; 44,640&lt; 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

答案 11 :(得分:0)

为了简化这一点而不失一般性,

日(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位。

编辑:猖獗说基本相同的事情。这样就可以获得最小的表示,它比按位交错表示要小。