Как преобразовать очень длинную строку в удвоенную в C

Я хочу преобразовать очень длинную строку чисел в двойное переносимым способом в C. В моем случае, переносимый означает, что он будет работать в Linux и Windows. Моя конечная цель состоит в том, чтобы иметь возможность упаковать строку чисел в 8-байтовый двойник и fwrite/fread в / из двоичного файла. Номер всегда без знака.

Я использую эту строку для упаковки четырехзначного года, двухзначного месяца, двухзначного дня, четырехзначного ЧЧ: ММ, однозначной переменной и десятизначного значения. Итак, пытаемся упаковать 23 байта в 8 байтов.

Я попробовал все стандартные вещи:

char myNumAsString[] = "1234567890123456789";

char *ptr;
char dNumString[64];
double dNum;


dNum = atol(myNumAsString);
sprintf(dNumString, "%lf", dNum);

dNum = atof(myNumAsString);
sprintf(dNumString, "%lf", dNum);

dNum = strtod(myNumAsString, &ptr);
sprintf(dNumString, "%lf", dNum);

sscanf(myNumAsString, "%lf", &dNum);
sprintf(dNumString, "%lf", dNum);

И ни одна из этих работ; все они округляют последние несколько цифр. Любой портативный способ сделать это?

2 ответа

Решение

Воспользуйтесь тем, что часть строки является меткой времени, а не каким-либо набором цифр.

С 60 минут, 24 часа, 365,25 дней / год, y лет, цифры и 10 цифр, есть 60*24*365.25*y*10*pow(10,10) комбинации или о 5.3e16 * y

8-байтовое, 64-битное число имеет 1.8e19 комбинации. Так что, если диапазон лет составляет 350 или меньше (например, с 1970 по 2320), все будет в порядке.

Предполагая временную метку Unix, и OP может преобразовать строку времени в time_t (проверять, выписываться mktime()) ....

time_t epoch = 0;  // Jan 1, 1970, Adjust as needed.

uint64_t pack(time_t t, int digit1, unsigned long long digit10) {
  uint64_t pack = digit1 * 10000000000 + digit10;
  time_t tminutes = (t - epoch)/60;

  pack += tminutes*100000000000;
  return pack;
}

Обратный, чтобы распаковать.


Или более полная портативная упаковка (код не проверен)

#include <time.h>
// pack 19 digit string
// "YYYYMMDDHHmm11234567890"
uint64_t pack(const char *s) {
  struct tm tm0 = {0};
  tm0.tm_year = 1970 - 1900;
  tm0.tm_mon = 1-1;
  tm0.tm_mday = 1;
  tm0.tm_isdst = -1;
  time_t t0 = mktime(&tm0);  // t0 will be 0 on a Unix system
  struct tm tm = {0};
  char sentinal;
  int digit1;
  unsigned long long digit10;
  if (strlen(s) != 4+2+2+2+2+1+10) return -1;
  if (7 != sscanf(s, "%4d%2d%2d%2d%2d%1d%10llu%c", &tm.tm_year,
          &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min,
          &digit1, &digit10, &sentinal)) return -1;
  tm.tm_year -= 1900;
  tm.tm_mon--;
  tm.tm_isdst = -1;
  time_t t = mktime(&tm);

  double diff_sec = difftime(t, t0);
  unsigned long long diff_min= diff_sec/60;
  return diff_min * 100000000000 + digit1*10000000000ull + digit10;
}

Вы можете сохранить некоторые биты, как только узнаете, что числа не могут иметь никакого значения.

  • ЧЧ: ММ: 0<= ЧЧ <=23 <32: 5 бит, 0 <= ММ <= 59 <64: 6 бит
  • DD: 1 <= DD <= 31 < 32: 5 бит
  • мм (месяц): 1 <= мм <= 12 < 16: 4 бита

Таким образом, вместо 8 байтов вам нужно только 20 битов, что меньше 3 байтов.

  • ГГГГ: вам действительно нужно принять любой год между 0 и 9999??? Если бы вы могли ограничить интересную часть только 2 веками, 8 битов было бы достаточно.

Таким образом, полная дата может занимать всего 4 байта вместо 12.

Но если вы хотите добавить к этой переменной 10-значное число + 1, это не будет стоять в 4 оставшихся байтах, потому что наибольшее значение uint32_t составляет 4294967295, достаточное для любого 9-значного числа и примерно для половины 10-значного числа.

Если бы 32 года было достаточно, вы могли бы представить до 34359738360, то есть 10 цифр и переменную, принимающую значения 0 1 или 2

Давайте посмотрим на это более точно; преобразования будут:

uint64_t timestamp;
uint8_t minute(uint64_t timestamp) { return timestamp & 0x3f; }
uint8_t hour(uint64_t timestamp) { return (timestamp >> 6) & 0x1f; }
uint8_t day(uint64_t timestamp) { return (timestamp >> 11) & 0x1f; }
uint8_t month(uint64_t timestamp) { return (timestamp >> 16) & 0x1f; }
uint8_t year(uint64_t timestamp) { return orig_year + ((timestamp >> 20) & 0x3f); } // max 64 years
uint64_t ten_digits(uint64_t timestamp) { return orig_year + ((timestamp >> 26) & 0x7FFFFFFFF); }
uint8_t var(uint64_t timestamp) { return (timestamp >> 61) & 0x7); } // 8 values for the one digit variable

Если вы можете принять только 4 значения для однозначной переменной, конечная часть становится:

uint8_t year(uint64_t timestamp) { return orig_year + ((timestamp >> 20) & 0x7f); } // max 128 years
uint64_t ten_digits(uint64_t timestamp) { return orig_year + ((timestamp >> 27) & 0x7FFFFFFFF); }
uint8_t var(uint64_t timestamp) { return (timestamp >> 61) & 0x3); } // 4 values for the one digit variable

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

Другие вопросы по тегам