Какое наименьшее количество байтов может хранить метку времени?

Я хочу создать свою собственную структуру данных метки времени на C.

ДЕНЬ (0–31), ЧАС (0–23), МИНУТА (0–59)

Какая наименьшая возможная структура данных?

Ответов (12)

Решение

Что ж, вы можете упаковать все это в 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 .]

Для описываемого вами варианта использования (минутное разрешение времени в диапазоне 31 день) я бы просто использовал 16-битный счетчик минут. Если вы сериализуете эти данные (на диск, в сеть), вы можете использовать целочисленное кодирование переменной длины для сохранения байтов для небольших значений.

Вы имеете в виду ЧАС 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 бит

Вы можете использовать битовое поле и использовать прагму, специфичную для компилятора / платформы, чтобы сохранить его жестко:

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

Но действительно ли вам это нужно? Разве стандартных функций времени не хватит? Битовые поля различаются в зависимости от архитектуры, заполнения и так далее ... не переносимая конструкция.

Примечание: исходный вопрос был отредактирован, и месяц больше не нужен. Исходные расчеты были ниже:

Это просто вопрос того, сколько вычислений вы хотите выполнить. Самый простой способ упаковать его - создать свой собственный тип и использовать следующую математику для преобразования из и в соответствующее целое число:

Допустимые диапазоны:

Месяц: 1-12 -> (0-11) +1
День: 1-31 -> (0-30) +1
Часы: 0-24
Минуты: 0-60

Вы можете выбрать порядок хранения значений (я сохраню его в указанном выше порядке).

Месяц-1 День-1 час Минуты
(0-11) (0-30) (0-23) (0-59)

Сделайте небольшое умножение / деление, чтобы преобразовать значения, используя следующую формулу в качестве руководства:

значение = (((Месяц - 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
--- Месяц --- ---- День ------- --- Час --- --Минуты ---

Если вам наплевать на месяц, самое лучшее, что вы можете сделать, это:

значение = ((День - 1) * 24 + Час) * 60 + Минуты

оставляя вас с диапазоном от 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
---- День ------- --- Час --- --Минуты ---

5 бит для дня плюс 5 бит для часа плюс 6 бит для минуты равны короткому беззнаковому значению. Любая дальнейшая упаковка не приведет к уменьшению требуемого дискового пространства и увеличит сложность кода и использование ЦП.

60 минут / час означает, что вам понадобится как минимум 6 бит для хранения минуты (с 59-й минуты == 111011b), а 24 часа / день означают еще 5 бит (23-й час == 10111b). Если вы хотите учитывать любой из (возможно) 366 дней в году, вам понадобится еще 9 бит (366-й день (365, когда день 1 == 0) == 101101101b). Итак, если вы хотите сохранить все в чисто доступном формате, вам понадобится 20 бит == 3 байта. В качестве альтернативы, добавление поля «Месяц» приведет к увеличению общего возможного значения «Дней» с 366 до 31 - до 5 битов, с дополнительными 4 битами для месяца. Это также даст вам 20 бит или 3 байта с 4 свободными битами.

И наоборот, если вы отслеживаете дату всего в минутах от некоторой начальной даты, 3 байта дадут вам разрешение 16 777 215 минут, прежде чем вы снова вернетесь к 0 - это примерно 279620 часов, 11650 дней и примерно 388 месяцев, и это использует все 24 бита. Вероятно, это лучший способ, если вас не волнуют секунды, и если вы не против потратить немного времени на выполнение, чтобы интерпретировать час, день и месяц. И это было бы намного проще увеличить!

просто чтобы предложить альтернативу:

  • если вам нужно только минутное разрешение,
  • и вы не пересекаете границы дат (месяц / год)
  • и ваши сообщения последовательны с гарантированной доставкой

тогда вы можете сохранить отметку времени как смещение от отметки времени последнего сообщения.

В этом случае вам нужно только достаточно битов, чтобы удерживать максимальное количество минут между сообщениями. Например, если вы отправляете сообщения с интервалом не более 255 минут, тогда будет достаточно одного байта.

Обратите внимание, однако, что самое первое сообщение может потребовать включения абсолютной отметки времени в полезную нагрузку для синхронизации.

[я не говорю, что это хорошее решение - оно довольно хрупкое и делает много предположений - просто альтернативное]

В общем, вы можете вычислить этот ответ следующим образом (где log2 - логарифм по основанию 2, то есть количество битов):

  • Если вы хотите использовать сдвиги и маски для ввода и вывода данных, возьмите log2 () количества возможных значений для каждого поля, округлите (чтобы получить биты), сложите результаты (чтобы получить общее количество битов), разделите на восемь (всего байтов, w. дробных байтов) и снова округлить (общее количество байтов).

    log2 (60) + log2 (60) + log2 (24) + log2 (31) + log2 (12) = 6 + 6 + 5 + 5 + 4 = 26 бит = 4 байта

  • Если вы хотите вводить и выводить поля путем умножения и добавления / деления и по модулю, умножьте количество возможных значений для каждого поля и возьмите log2 () из этого, разделите на восемь и округлите в большую сторону.

    log2 (60 * 60 * 24 * 31 * 12) = 24,9379 бит = 4 байта

  • Вы можете сэкономить крошечный дополнительный объем пространства, комбинируя неизоформные поля (например, сохраняя день года, а не месяц и день месяца), но это редко того стоит.

    log2 (60 * 60 * 24 * 366) = 24,91444 бит = 4 байта

- MarkusQ «научи человека ловить рыбу»

Что ж, не считая лишних 24 ЧАСА и 60 МИНУТ, мы имеем 31 x 24 x 60 = 44 640 возможных уникальных значений времени. 2 ^ 15 = 32,768 <44,640 <65,536 = 2 ^ 16, поэтому для представления этих значений нам понадобится не менее 16 бит (2 байта).

Если мы не хотим выполнять арифметические операции по модулю для доступа к значениям каждый раз, нам нужно обязательно хранить каждое в своем собственном битовом поле. Нам нужно 5 бит для хранения ДНЯ, 5 бит для хранения ЧАСА и 6 бит для хранения МИНУТЫ, которая по-прежнему умещается в 2 байта:

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

Включение МЕСЯЦА увеличит наши уникальные значения времени в 12 раз, давая 535 680 уникальных значений, для которых потребуется не менее 20 бит для хранения (2 ^ 19 = 524 288 <535 680 <1048 576 = 2 ^ 20), что требует как минимум 3 байтов.

Опять же, чтобы избежать арифметики по модулю, нам нужно отдельное битовое поле для МЕСЯЦА, которое должно требовать всего 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 сказал в основном то же самое. И это дает вам минимальное представление, которое меньше, чем представление побитового чередования.

Почему бы просто не использовать (4-байтовый?) Вывод time() функции C NULL в качестве аргумента. Это просто время эпохи Unix (то есть количество секунд с 1 января 1970 года). Как и ответ Джо, он дает вам гораздо больше возможностей для роста, чем любой ответ, который пытается упаковать месяцы, дни и годы в кусочки. Это стандартно. Преобразование time_t переменной в фактическое время тривиально в стандартном C (по крайней мере, в Unix), и в большинстве случаев, если у вас есть структура данных, предназначенная для хранения 3-байтовой переменной, она в любом случае может быть округлена до 4 байтов.

Я знаю, что вы пытаетесь сильно оптимизировать размер, но 4 байта чертовски мало. Даже если вы отсечете верхний байт, вы все равно получите 194 дня различных значений времени.

Вы можете получить еще больше, если выделите время time(NULL) и разделите его на 60 перед сохранением, усекая его до минуты и сохраняя это. 3 байта из них дают вам, как показано выше, 388 месяцев, а 2 байта вы можете хранить 45 дней.

Я бы выбрал 4-байтовую версию просто потому, что я не вижу разницы между 2, 3 и 4 байтами как существенную или жизненно важную для любой запущенной программы или нет (если это не загрузчик). Его проще получить и проще в обращении, и, вероятно, он в конечном итоге избавит вас от многих головных болей.

РЕДАКТИРОВАТЬ: опубликованный мной код не работал. Я спал 3 часа, и со временем я выясню, как правильно делать бит-твиддлинг. А пока вы можете реализовать это самостоятельно.