使用精度从给定误差范围导出的方法打印双精度值






3.89/5 (5投票s)
在本文中,我们将考虑一种根据给定的误差范围导出精度来打印双精度值的方法。
引言
通常,C 语言有一种非常强大的打印不同值的方法;例如,在下面的示例中使用打印模式,如 1.3lf
。
double value = 1.543605908721; printf("Value 1.3lf", value);
这种方法的唯一问题是,这种定义打印格式的方式绝对不灵活。
如果有人想要根据程序本身的输入,以不同的格式打印值,该怎么办?
/*One of examples is printing an array of doubles with the given error bound.*/
在本文中,我们将详细讨论这个问题。
最初,这篇文章的灵感来自于打印小数点后“正确的数字”之前的概率值的问题。“正确的数字”是指与预设的误差范围相对应的数字。
以下所有示例都改编自 Markov Reward Model Checker (MRMC) 工具源,这些源由我编写,并根据 GPL 许可自由分发。
任务
给定一个 double
值数组 probs
和一个误差范围 error_bound
(所有数组元素相同),我们希望打印这些值,排除小数点后所有相对于误差范围不重要的数字。
例如
double * probs = {0.565820439, 0.33339}; double error_bound = 1.0e-3; //We would like to have the following output: //Result: ( 0.5658, 0.3333)
请注意,上面的输出是由小数点后的第 4 位数字组成的,而不是像人们期望的那样是第 3 位。原因是,例如,对于差值为 1.1e-3
(大于给定的 error_bound
)的值 0.565820439
和 0.564710439
,如果将它们打印到小数点后的第 3 位数字,将无法识别出这种差异:0.565
和 0.564
。
解决方案
主要任务,尽管听起来可能令人惊讶,是计算应该打印到小数点后的哪一位数字。考虑以下计算它的函数
/** * This method is used for getting * the printing pattern from the error bound. * NOTE: If we have error 1.231e-10 then the precision * is 10+1 digits after the decimal point, * but not 13 (as for 1.231e-10 == 1231.0e-13)! * @param error_bound: the error bound * @return: the precision for the printing pattern by taking the position * of the first significant digit after the decimal point + 1 */ int get_error_bound_precision(double error_bound){ int precision = 0; BOOL not_found = TRUE; double integer = 0, fraction = 0; //NOTE: If we have error 1.1e-10 then the presision //is 10 digits after the decimal point, but not 11. while ( not_found ){ error_bound *= 10; fraction = modf(error_bound, &integer); if ( integer > 0.0 ){ not_found = FALSE; } precision++; } //We need this +1 here to add an extra digit return precision + 1; }
这里所做的是,基本上是沿着 error_bound
的数字向下寻找第一个非零的数字。
剩下的就相当容易了,我们必须创建用于打印 double
值的模式,这可以通过使用 sprintf
运算符来完成,请参见下面的函数
/** * This method is used for printing the state * probabilities vector with the given error bound. * @param size: the size of the probabilities vector * @param probs: the vector of probabilities * @param error_bound: the error bound */ void print_state_probs(int size, double * probs, double error_bound ){ printf("Result: "); //Calculate the right pattern according to the precision char buffer[255]; sprintf(buffer, "%%1.%dlf", get_error_bound_precision(error_bound)); print_pattern_vec_double(buffer, size, probs); }
然后调用以下简单函数,该函数将使用我们之前导出的模式来打印 double
数组。
/** * This method is used for printing an array * of double values according to the given pattern. * @param pattern: the pattern for printing each array element * @param length: the array lenght * @param vec: the array of doubles to be printed */ void print_pattern_vec_double(const char * pattern, int length, double * vec) { int i; printf("( "); for( i = 0 ; i < length ; i++ ){ //NOTE: this is where the pattern for printing is used printf( pattern, vec[i] ); if (i != length-1) printf(", "); } printf(" )\n"); }
结论
在这里,我们考虑了一种最简单的打印 double
值的方法,其精度由某个误差范围定义。
下面,我将提及另外两种解决该问题的方法,这些方法是我在调查过程中考虑过的
- 具有
double error_bound = 1.47003454e-10
并打印具有所需精度的值,同时考虑到error_bound
的最后一个非零数字是不可行的,因为实际上存储在double error_bound
变量中的值并不完全是1.47003454e-10
,而是类似于1.47003454000000000000329999961234769e-10
,这是所需的误差加上一些噪声。 - 使用 IEEE 浮点标准 从
error_bound
计算所需的精度并没有真正的帮助。从第一眼看,有一部分可以让你了解数字的指数部分,但这都是关于 2 的幂,而我们感兴趣的是10
的幂。