mal*_*lat 5 c floating-point precision
这是我原帖的后续。但为了清楚起见,我会重复一遍:
根据 DICOM 标准,可以使用十进制字符串的值表示来存储一种类型的浮点。请参阅表6.2-1。DICOM 值表示:
十进制字符串:表示定点数或浮点数的字符串。定点数应仅包含字符 0-9 以及可选的前导“+”或“-”和可选的“.”。来标记小数点。浮点数应按照 ANSI X3.9 中的定义传送,用“E”或“e”表示指数的开始。十进制字符串可以用前导或尾随空格填充。不允许嵌入空格。
“0”-“9”、“+”、“-”、“E”、“e”、“.” 和默认字符曲目的空格字符。最大 16 字节
标准是说文本表示是定点与浮点。该标准仅涉及值在 DICOM 数据集本身中的表示方式。因此,不需要将定点文本表示加载到定点变量中。
所以现在很明显,DICOM 标准隐含地推荐double(IEEE 754-1985) 来表示Value Representation类型Decimal String(最多 16 位有效数字)。我的问题是如何使用标准 CI/O 库将这个二进制表示从内存转换回 ASCII 到这个有限大小的字符串?
从互联网上的随机来源来看,这并非易事,但普遍接受的解决方案是:
printf("%1.16e\n", d); // Round-trippable double, always with an exponent
Run Code Online (Sandbox Code Playgroud)
或者
printf("%.17g\n", d); // Round-trippable double, shortest possible
Run Code Online (Sandbox Code Playgroud)
当然,这两个表达式在我的情况下都是无效的,因为它们可以产生比我限制的最大16 字节长得多的输出。那么在将任意双精度值写入有限的 16 字节字符串时,最小化精度损失的解决方案是什么?
编辑:如果这不清楚,我需要遵循标准。我不能使用十六进制/uuencode 编码。
编辑 2:我正在使用 travis-ci 进行比较,请参阅:here
到目前为止,建议的代码是:
我在这里看到的结果是:
compute1.c 导致总和误差为: 0.0095729050923877828compute2.c 导致总和误差为: 0.21764383725715469compute3.c 导致总和误差为: 4.050031792674619compute4.c 导致总和误差为: 0.001287056579548422因此compute4.c导致最佳精度(0.001287056579548422 < 4.050031792674619),但总执行时间增加了三倍(x3)(仅在调试模式下使用time命令测试)。
这比最初想象的要棘手。
考虑到各种极端情况,似乎最好尝试高精度,然后根据需要进行降低。
任何负数的打印结果与正数相同,但由于'-'.
'+'字符串开头或之后都不需要符号'e'。
'.'不需要。
考虑到如此多的sprintf()极端情况,除了数学部分之外,使用任何其他方法都是危险的。给定各种舍入模式等,将繁重的编码留给完善的函数。FLT_EVAL_METHOD
当一次尝试太长超过 1 个字符时,可以保存迭代。例如,如果尝试使用精度 14,结果宽度为 20,则无需尝试精度 13 和 12,只需转到 11。
由于删除 而导致的指数缩放,必须在 1) 避免注入计算错误 2) 将 a 递减至其最小指数以下之后'.'进行。sprintf()double
最大相对误差小于 2,000,000,000 分之一-1.00000000049999e-200。平均相对误差约为50,000,000,000分之一。
最高的 14 位精度出现在以12345678901234e116-2 位开头的数字中。
static size_t shrink(char *fp_buffer) {
int lead, expo;
long long mant;
int n0, n1;
int n = sscanf(fp_buffer, "%d.%n%lld%ne%d", &lead, &n0, &mant, &n1, &expo);
assert(n == 3);
return sprintf(fp_buffer, "%d%0*llde%d", lead, n1 - n0, mant,
expo - (n1 - n0));
}
int x16printf(char *dest, size_t width, double value) {
if (!isfinite(value)) return 1;
if (width < 5) return 2;
if (signbit(value)) {
value = -value;
strcpy(dest++, "-");
width--;
}
int precision = width - 2;
while (precision > 0) {
char buffer[width + 10];
// %.*e prints 1 digit, '.' and then `precision - 1` digits
snprintf(buffer, sizeof buffer, "%.*e", precision - 1, value);
size_t n = shrink(buffer);
if (n <= width) {
strcpy(dest, buffer);
return 0;
}
if (n > width + 1) precision -= n - width - 1;
else precision--;
}
return 3;
}
Run Code Online (Sandbox Code Playgroud)
测试代码
double rand_double(void) {
union {
double d;
unsigned char uc[sizeof(double)];
} u;
do {
for (size_t i = 0; i < sizeof(double); i++) {
u.uc[i] = rand();
}
} while (!isfinite(u.d));
return u.d;
}
void x16printf_test(double value) {
printf("%-27.*e", 17, value);
char buf[16+1];
buf[0] = 0;
int y = x16printf(buf, sizeof buf - 1, value);
printf(" %d\n", y);
printf("'%s'\n", buf);
}
int main(void) {
for (int i = 0; i < 10; i++)
x16printf_test(rand_double());
}
Run Code Online (Sandbox Code Playgroud)
输出
-1.55736829786841915e+118 0
'-15573682979e108'
-3.06117209691283956e+125 0
'-30611720969e115'
8.05005611774356367e+175 0
'805005611774e164'
-1.06083057094522472e+132 0
'-10608305709e122'
3.39265065244054607e-209 0
'33926506524e-219'
-2.36818580315246204e-244 0
'-2368185803e-253'
7.91188576978592497e+301 0
'791188576979e290'
-1.40513111051994779e-53 0
'-14051311105e-63'
-1.37897140950449389e-14 0
'-13789714095e-24'
-2.15869805640288206e+125 0
'-21586980564e115'
Run Code Online (Sandbox Code Playgroud)