如何在便携式 C 中将很长的字符串转换为双精度
How convert very long string to double in portable C
我想在 C 中以可移植的方式将很长的数字字符串转换为双精度数。在我的例子中,可移植意味着它可以在 Linux 和 Windows 中工作。我的最终目标是能够将一串数字打包成一个 8 字节的双精度和 fwrite/fread to/from 二进制文件。该数字始终是无符号的。
我正在使用此字符串来打包 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);
和 none 这些作品;他们都把最后几个数字四舍五入。有什么便携的方法可以做到这一点?
利用字符串的一部分是时间戳而不是任何数字集。
有60分钟,24小时,365.25days/year,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;
}
只要知道数字不能是任何值,就可以节省一些位。
- HH:MM : 0<=HH<=23 <32 : 5 位, 0 <= MM <= 59 <64 : 6 位
- DD:1 <= DD <= 31 < 32:5 位
- mm(月):1 <= mm <= 12 < 16:4 位
因此,或者 8 个字节,您只需要少于 3 个字节的 20 位。
- YYYY:你真的需要接受 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
如果计算一个纪元以来的绝对分钟数,您甚至可以节省一些位,但计算会复杂得多。
我想在 C 中以可移植的方式将很长的数字字符串转换为双精度数。在我的例子中,可移植意味着它可以在 Linux 和 Windows 中工作。我的最终目标是能够将一串数字打包成一个 8 字节的双精度和 fwrite/fread to/from 二进制文件。该数字始终是无符号的。
我正在使用此字符串来打包 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);
和 none 这些作品;他们都把最后几个数字四舍五入。有什么便携的方法可以做到这一点?
利用字符串的一部分是时间戳而不是任何数字集。
有60分钟,24小时,365.25days/year,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;
}
只要知道数字不能是任何值,就可以节省一些位。
- HH:MM : 0<=HH<=23 <32 : 5 位, 0 <= MM <= 59 <64 : 6 位
- DD:1 <= DD <= 31 < 32:5 位
- mm(月):1 <= mm <= 12 < 16:4 位
因此,或者 8 个字节,您只需要少于 3 个字节的 20 位。
- YYYY:你真的需要接受 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
如果计算一个纪元以来的绝对分钟数,您甚至可以节省一些位,但计算会复杂得多。