Ответов (12)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-битный счетчик минут. Если вы сериализуете эти данные (на диск, в сеть), вы можете использовать целочисленное кодирование переменной длины для сохранения байтов для небольших значений.
- Месяц: диапазон 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 ---- День ------- --- Час --- --Минуты ---
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 часа, и со временем я выясню, как правильно делать бит-твиддлинг. А пока вы можете реализовать это самостоятельно.