问题
朋友要用数值算例验证舍入误差对算法稳定性的影响,实践中遇到的第一个问题是:C/C++如何只保留4位有效数字(significant figures)?
如果接触过Java/JS等语言,保留小数点后几位是非常容易的,调用setScale
、toFixed
等函数就可以,还能指定舍入模式。但是对于C/C++,如何保留指定有效位数呢?
C/C++保留指定有效数字
在C/C++语言中,精度的概念只在输出(流)中出现,因此实现的一个思路是:按照指定精度输出字符串,再转换成数字。
C语言指定有效数字位数
先看C语言的解决方案。
C语言的printf
系列函数可以指定输出格式,打印成字符串后可用atof
转换成数字。于是指定有效数字的C语言实现版本如下:
#include <stdio.h> #include <stdlib.h> double convert1(int precision, double val) { char buffer[128]; sprintf(buffer, "%.*g", precision, val); return atof(buffer); }
使用代码验证:
int main(int argc, char* argv[]) { double f1 = 1235.46698; printf("origin: %.12f, converted: %.12f\n", f1, convert1(4, f1)); double f2 = 1.23546698; printf("origin: %.12f, converted: %.12f\n", f2, convert1(4, f2)); double f3 = 0.00123546698; printf("origin: %.12f, converted: %.12f\n", f3, convert1(4, f3)); double f4 = 0.0000123546698; printf("origin: %.12f, converted: %.12f\n", f4, convert1(4, f4)); return 0; }
输出结果如下:
origin: 1235.466980000000, converted: 1235.000000000000 origin: 1.235466980000, converted: 1.235000000000 origin: 0.001235466980, converted: 0.001235000000 origin: 0.000012354670, converted: 0.000012350000
均保留了4位有效数字,与预期相符。
C++语言指定有效数字位数
C++使用范型(Generics)对标准库进行了大重构,在语法方面相对于C可读性提升不少。
以下是C++使用stringstream
转换数字的代码:
#include <sstream> template<int PRECISION> double convert2(double input) { std::stringstream is; double res; is.precision(PRECISION); is << input; is >> res; return res; }
使用代码验证:
int main(int argc, char* argv[]) { std::cout.precision(12); std::cout.setf(std::ios::fixed, std:: ios::floatfield); double f1 = 1235.46698; std::cout << "origin: " << f1 << ", converted: " << convert2<4>(f1) << std::endl; double f2 = 1.23546698; std::cout << "origin: " << f2 << ", converted: " << convert2<4>(f2) << std::endl; double f3 = 0.00123546698; std::cout << "origin: " << f3 << ", converted: " << convert2<4>(f3) << std::endl; double f4 = 0.0000123546698; std::cout << "origin: " << f4 << ", converted: " << convert2<4>(f4) << std::endl; return 0; }
其输出结果与C语言版本一致。
总结
C/C++中,整数、浮点数等基本类型没有原生的格式化支持,需要借助输入输出的格式化函数才能达到保留指定有效数字的目的。
与有效数字类似的问题还有:
- C/C++中如何保留小数点后指定位数?
- C/C++中如何实现FLOOR/CEILING、UP/DOWN、HALF_UP/HALF_DOWN等舍入模式(Rounding Mode)?
更好的解决方案:
double convert(double x,int percision){
double res=int(x);
x-=res;
for(int i=0;i<percision;i++)
{
x*=10;
res=res*10+int(x);
x-=int(x);
}
for(int i=0;i<percision;i++)
res/=10.0;
return res;
}
你这种方式是小数点后多少位,不是有效数字。
小数点后多少位,有比你这更简单的代码