如何在便携式C中将非常长的字符串转换为double

时间:2015-08-22 18:51:52

标签: c atof strtod

我希望在C语言中以便携方式将一长串数字转换为双数。在我的例子中,可移植意味着它可以在Linux和Windows中运行。我的最终目标是能够将一串数字打包成一个8字节的双精度数据并将fwrite / fread打包成二进制文件。该号码始终是未签名的。

我使用此字符串打包4位数年份,2位数月份,2位数日期,4位数字HH:MM,1位数变量和10位数值。因此,尝试将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 个答案:

答案 0 :(得分: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;
}

答案 1 :(得分:0)

只要您知道数字不能是任何值,就可以保存一些位。

  • HH:MM:0 <= HH <= 23 <32:5位,0 <= MM <= 59 <64:6位
  • DD:1&lt; = DD&lt; = 31&lt; 32:5位
  • mm(月):1 <= mm&lt; = 12&lt; 16:4位

所以相反或8个字节,你只需要20个小于3个字节的位。

  • YYYY:你真的需要接受0到9999之间的任何一年???如果你可以将有趣的部分限制在2个世纪,那么8位就足够了。

所以一个完整的日期可能只有4个字节而不是12个字节。

但是如果你想要添加一个10位数字+ 1个变量,那么它将不会存在于剩下的4个字节中,因为最大的uint32_t对于任何9位数字都是4294967295,大约是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

如果计算了自纪元以来的绝对分钟数,你甚至可以节省一些比特,但计算会更复杂。