C++四舍五入保留N位小数

时间:2022-10-29 09:43:56

最近遇到这个问题,不难吧,不过搜网上的东西,没看到比较合心水的答案,自己搞了两种做法,请君侧耳为我听。

乘10法

这种想法最容易想到,就是将数字乘10,乘N次,加上0.5后取整,最后再除回去。加上0.5取整应该很容易理解,是等价于四舍五入的,因为X.49999……+0.5后取整是不会进位的,结果是X;而X.5000……1+0.5后取整会变成X+1(当然忽略double或float能表示的精度)。

比如:

23.3451保留两位小数,那么连续乘10,2次,得到2334.5,加上0.5后取整得到23345,再除回去,变成23.35,是不是对了呢?

简单代码如下:

#include <stdio.h>
#include <cmath>

typedef long long LL;

double round(double number, unsigned int bits) {
LL integerPart = number;
number -= integerPart;
for (unsigned int i = 0; i < bits; ++i)
number *= 10;
number = (LL) (number + 0.5);
for (unsigned int i = 0; i < bits; ++i)
number /= 10;
return integerPart + number;
}

int main() {
printf("%.2f\n", round(23.451528739, 1));
printf("%.3f\n", round(23.451528739, 2));
printf("%.4f\n", round(23.451528739, 3));
printf("%.5f\n", round(23.451528739, 4));
printf("%.6f\n", round(23.451528739, 5));
printf("%.7f\n", round(23.451528739, 6));
printf("%.8f\n", round(23.451528739, 7));

return 0;
}

注意,printf也可以做到按多少位小数四舍五入输出,所以在输出的时候,我总是把printf输出的精度比我自己的函数高1个位,可以看出那个位总是为0,表示是我写的函数起的作用(而不是printf的影响),上面的示例程序的输出结果为:

23.50
23.450
23.4520
23.45150
23.451530
23.4515290
23.45152870

问题来了

这个做法有什么问题呢?
注意到上面是先将double的整数部分保留起来了,然后后面只是对number原来的小数部分乘以10而已,为什么要这样做呢?
因为,注意到后面做四舍五入的关键一步,要取整,如果不先把已知的整数部分去掉,那么总体乘以10^N后很容易就会溢出整数所能表示的范围了!

也就是说,这种方法的局限在于,需要将double转成数值范围相对很小的long long来表示,所以如果需要精确到很大的小数位的话,就坑了,因为long long最多能表示10^18左右的数字,如果想四舍五入到小数点后20+位的话……其实这样变态的需求一般不会有,而且也不可能会有,因为double本身的精确位数只能到15-16(参考:float与double的范围和精度)。

再者,注意原始的整数部分也是用long long来保存的,这样对于double的范围几乎就限制死了……有没有更好的方法呢?

会这样问的,肯定就是有啦,我发现可以使用cmath库的floor向下取整函数来辅助取整,而不需要通过long long类型来取整,这样的话,上面所说的两个约束就不存在了!!!

double round(double number, unsigned int bits) {
double integerPart = floor(number);
number -= integerPart;
for (unsigned int i = 0; i < bits; ++i)
number *= 10;
number = floor(number + 0.5);
for (unsigned int i = 0; i < bits; ++i)
number /= 10;
return integerPart + number;
}

使用C++的输出流功能

C++的输出流可以通过setprecision(N)和fixed合作来进行精确到小数点后第N为四舍五入!
但是只能输出到控制台???我想做成像上面那样的一个函数,结果作为返回值返回。
这个时候简单借助stringstream就好了!!!示例如下:

#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;

double round(double number, unsigned int bits) {
stringstream ss;
ss << fixed << setprecision(bits) << number;
ss >> number;
return number;
}

int main() {
double number = 3.1415926535897932;
cout << fixed << showpoint << setprecision(15);
cout << "一开始number = " << number << endl;

for (int i = 0; i < 15; ++i) {
cout << "number保留" << i << "位小数后为: "
<< round(number, i) << endl;
}

return 0;
}

输出为:

一开始number = 3.141592653589793
number保留0位小数后为: 3.000000000000000
number保留1位小数后为: 3.100000000000000
number保留2位小数后为: 3.140000000000000
number保留3位小数后为: 3.142000000000000
number保留4位小数后为: 3.141600000000000
number保留5位小数后为: 3.141590000000000
number保留6位小数后为: 3.141593000000000
number保留7位小数后为: 3.141592700000000
number保留8位小数后为: 3.141592650000000
number保留9位小数后为: 3.141592654000000
number保留10位小数后为: 3.141592653600000
number保留11位小数后为: 3.141592653590000
number保留12位小数后为: 3.141592653590000
number保留13位小数后为: 3.141592653589800
number保留14位小数后为: 3.141592653589790

分析一下,这个输出流的功能是C++的标准库支持的,所以其可靠性是可以信任的,没有上面的做法那么多控制的考虑。

问题来了

它库是怎么实现的????

真心好奇,希望有知道的朋友发邮件告诉我一声,万谢!

不然我只能等有空去找源码看了^_^