C++11转化double为string是一件很容易的事情。
方法:
1:使用C中的sprintf函数,这里就不说了。
2:使用std::ostringstream。这个与std::cout是一样的。这个在C++11以前就已经支持了的。这个得出的结果与使用std::cout的结果是一样的。
3:从C++11开始,标准库提供了std::to_string辅助函数转化各类型为一个字符串。
std::ostringstream和std::to_string
但是std::ostringstream和std::to_string使用的结果就有些很奇怪的差异了。主要有:
1:std::string得到的结果始终是小数位必然是6!
2:默认情况下,std::ostringstream对double使用的是6位精度。这里精度值的是整数位和小数位个数和。但是精度是可以设置的。这里统一谈论默认的情况。
也就是说,如果参数精度超过6位的话,那么会将数值四舍五入,然后丢弃多余的位数!
具体来说是这样的:
1:如果整数位不足6位,而整体精度超过了6,那么小数位四舍五入,然后截断多余的位数。
2:如果是整数位超过了6,那么舍弃小数位,然后按照科学计数法保存。
比如:
序号 | double原值 | std::ostringstream结果 |
1 | 1.0000000000001 | 1 |
2 | 0.12345678 | 0.123457 |
3 | 123456789.0000000000001 | 1.23457e+08 |
4 | 123456789.1234567 | 1.23457e+08 |
5 | 0.0000000000001 | 1e-13 |
下面我们详细比较std::ostringstream和std::to_string使用的结果的差异。
这里是详细的测试代码,请注意,这里必须是C++11及以上版本!
1 1 #include <string>
2 2 #include <cassert>
3 3 #include <iostream>
4 4 #include <sstream>
5 5
6 6 std::string DoubleToStringByStdToString(double value)
7 7 {
8
9 8 const std::string& new_val = std::to_string(value);
10 9 return new_val;
11 10 }
12 11 ▫
13 12 std::string DoubleToStringByStringStream(double value)
14 13 {
15 14 std::ostringstream stream;
16 15 stream << value;
17 16 return stream.str();
18 17 }
19 18 ▫
20 19 void TestDoubleToStringByStdToString(const double value, const std::string& origin, const std::string& expect_str)
21 20 {
22 21 const std::string& val = DoubleToStringByStdToString(value);
23 22 std::cout << __FUNCTION__ << " --> original:" << origin
24 23 << ", std::cout:" << value
25 24 << ", std::to_string:" << val<< std::endl;
26 25 ▫
27 26 assert( val == expect_str);
28 27 }
29 28 ▫
30 29 void TestDoubleToStringByStringStream(const double value, const std::string& origin, const std::string& expect_str)
31 30 {
32 31 const std::string& val = DoubleToStringByStringStream(value);
33 32 std::cout << __FUNCTION__ << " --> original:" << origin
34 33 << ", std::cout:" << value
35 34 << ", std::stringstream:" << val<< std::endl;
36 35 ▫
37 36 assert( val == expect_str);
38 37 }
39 38
40 39 int main(int argc, char* argv[])
41 40 {
42 41 TestDoubleToStringByStdToString(0, "0", "0.000000");
43 42 TestDoubleToStringByStringStream(0, "0", "0");
44 43
45 44 TestDoubleToStringByStdToString(.0, ".0", "0.000000");
46 45 TestDoubleToStringByStringStream(.0, ".0", "0");
47 46
48 47 TestDoubleToStringByStdToString(0.0, "0.0", "0.000000");
49 48 TestDoubleToStringByStringStream(0.0, "0.0", "0");
50 49
51 50 TestDoubleToStringByStdToString(1.0, "1.0", "1.000000");
52 51 TestDoubleToStringByStringStream(1.0, "1.0", "1");
53 52
54 53 TestDoubleToStringByStdToString(1.0000008, "1.0000008", "1.000001");
55 54 TestDoubleToStringByStringStream(1.0000008, "1.0000008", "1");
56 55
57 56 TestDoubleToStringByStdToString(1.0000000000001,"1.0000000000001", "1.000000");
58 57 TestDoubleToStringByStringStream(1.0000000000001,"1.0000000000001", "1");
59 58
60 59 TestDoubleToStringByStdToString(0.0000000000001,"0.0000000000001", "0.000000");
61 60 TestDoubleToStringByStringStream(0.0000000000001,"0.0000000000001", "1e-13");
62 61
63 62 TestDoubleToStringByStdToString(0.12345678,"0.12345678", "0.123457");
64 63 TestDoubleToStringByStringStream(0.12345678,"0.12345678", "0.123457");
65 64
66 65 TestDoubleToStringByStdToString(100000000000.0000000000001,"100000000000.0000000000001", "100000000000.000000");
67 66 TestDoubleToStringByStringStream(100000000000.0000000000001,"100000000000.0000000000001", "1e+11");
68 67
69 68 TestDoubleToStringByStdToString(1e+11,"1e+11", "100000000000.000000");
70 69 TestDoubleToStringByStringStream(1e+11,"1e+11", "1e+11");
71 70
72 71 TestDoubleToStringByStdToString(123456.0000000000001, "123456.0000000000001", "123456.000000");
73 72 TestDoubleToStringByStringStream(123456.0000000000001, "123456.0000000000001", "123456");
74 73
75 74 TestDoubleToStringByStdToString(123456789.1234567,"123456789.1234567", "123456789.123457");
76 75 TestDoubleToStringByStringStream(123456789.1234567,"123456789.1234567", "1.23457e+08");
77 76
78 77 TestDoubleToStringByStdToString(123456789.0000000000001,"123456789.0000000000001", "123456789.000000");
79 78 TestDoubleToStringByStringStream(123456789.0000000000001,"123456789.0000000000001", "1.23457e+08");
80 79
81 80 return 0;
82 81 }
View Code
我们这里将结果整理出来如下表
序号 | double原值 | std::cout | std::ostringstream结果 | std::to_string()结果 |
1 | 0 | 0 | 0 | 0.000000 |
2 | .0 | 0 | 0 | 0.000000 |
3 | 0.0 | 0 | 0 | 0.000000 |
4 | 1.0 | 1 | 1 | 1.000000 |
5 | 1.0000008 | 1 | 1 | 1.000001 |
6 | 1.0000000000001 | 1 | 1 | 1.000000 |
7 | 0.0000000000001 | 1e-13 | 1e-13 | 0.000000 |
8 | 0.12345678 | 0.123457 | 0.123457 | 0.123457 |
9 | 100000000000.0000000000001 | 1e+11 | 1e+11 | 100000000000.000000 |
10 | 1e+11 | 1e+11 | 1e+11 | 100000000000.000000 |
11 | 123456.0000000000001 | 123456 | 123456 | 123456.000000 |
12 | 123456789.1234567 | 1.23457e+08 | 1.23457e+08 | 123456789.123457 |
13 | 123456789.0000000000001 | 1.23457e+08 | 1.23457e+08 | 123456789.000000 |
从上面的结果我们还可以发现一个关于std::to_string的特点
如果传入的double本身是科学记数法,to_string仍然可以执行转化,且得出的结果与该科学技术法表述的值转化的结果是一样的!
总结
虽然C++对关转化double为string提供的方法很多,但是的得出的结果不一样。所以在使用时应该统一方法,并且格外小心,如果是在对double很敏感的行业,那么应该对该操作封装,并提供足够的控制参数。
一种可参考的实现
从上面我们可以看出使用ostringstream或者to_string的方法,要么存在精度显示问题要么调整为科学计数法显示。这些都不是我们想要的。
所以我们可以使用ostringstream封装一个辅助函数,可以控制精度也可以控制科学计数法显示。
精度
ostringstream是可以控制精度的,函数原型如下:
std::ios_base::precision
第一版
1 std::string DoubleToString(const double value, unsigned int precision)
2 {
3 std::ostringstream out;
4 if (precision > 0)
5 out.precision(precision);
6
7 out << value;
8 return out.str();
9 }
10
11
12 int main(int argc, char* argv[])
13 {
14 std::cout << DoubleToString(0., 12) << std::endl;
15 std::cout << DoubleToString(0.0, 12) << std::endl;
16 std::cout << DoubleToString(.0, 12) << std::endl;
17 std::cout << DoubleToString(1.0, 12) << std::endl;
18 std::cout << DoubleToString(11234, 12) << std::endl;
19 std::cout << DoubleToString(0.12345, 12) << std::endl;
20 std::cout << DoubleToString(0.12345678, 12) << std::endl;
21 std::cout << DoubleToString(0.12345678, 9) << std::endl;
22 std::cout << DoubleToString(0.12345678, 8) << std::endl;
23 std::cout << DoubleToString(0.12345678, 6) << std::endl;
24 return 0;
25 }
View Code
这是测试结果的输出:
0
0
0
1
11234
0.12345
0.12345678
0.12345678
0.12345678
0.123457
第二版
多数情况下我们更加关注的是小数点后的几位数,所以我们调整参数控制小数点后位数。
1 #include <limits>
2 std::string DoubleToString(const double value, unsigned int precisionAfterPoint)
3 {
4 std::ostringstream out;
5 // 清除默认精度
6 out.precision(std::numeric_limits<double>::digits10);
7 out << value;
8
9 std::string res = std::move(out.str());
10 auto pos = res.find('.');
11 if (pos == std::string::npos)
12 return res;
13
14 auto splitLen = pos + 1 + precisionAfterPoint;
15 if (res.size() <= splitLen)
16 return res;
17
18 return res.substr(0, splitLen);
19 }
20
21 int main(int argc, char* argv[])
22 {
23 std::cout << DoubleToString(0., 12) << std::endl;
24 std::cout << DoubleToString(0.0, 12) << std::endl;
25 std::cout << DoubleToString(.0, 12) << std::endl;
26 std::cout << DoubleToString(1.0, 12) << std::endl;
27 std::cout << DoubleToString(11234, 12) << std::endl;
28 std::cout << DoubleToString(12345.12345678, 12) << std::endl;
29 std::cout << DoubleToString(12345.12345678, 9) << std::endl;
30 std::cout << DoubleToString(12345.12345678, 7) << std::endl;
31 std::cout << DoubleToString(12345.12345678, 8) << std::endl;
32 std::cout << DoubleToString(12345.12345678, 6) << std::endl;
33 std::cout << DoubleToString(12345.00000001, 7) << std::endl;
34 std::cout << DoubleToString(12345.00000001, 8) << std::endl;
35 std::cout << DoubleToString(12345.00000001, 6) << std::endl;
36 return 0;
37 }
View Code
这是测试结果的输出:
0
0
0
1
11234
12345.12345678
12345.12345678
12345.1234567
12345.12345678
12345.123456
12345.0000000
12345.00000001
12345.000000
第三版
更进一步的,我们一般默认情况下小数点后是是6位小数的。所以我们可以设置默认参数:
1 #include <limits>
2 std::string DoubleToString(const double value, unsigned int precisionAfterPoint = 6)
3 {
4 std::ostringstream out;
5 // 清除默认精度
6 out.precision(std::numeric_limits<double>::digits10);
7 out << value;
8
9 std::string res = std::move(out.str());
10 auto pos = res.find('.');
11 if (pos == std::string::npos)
12 return res;
13
14 auto splitLen = pos + 1 + precisionAfterPoint;
15 if (res.size() <= splitLen)
16 return res;
17
18 return res.substr(0, splitLen);
19 }
View Code
第四版
1:实际上第三版的实现存在一个BUG,即设置默认小数位后没有执行四舍五入!
2:性能。这个实现性能如何,是不是存在更佳的实现呢?
3:处理完成后,如果小数位全是0,该怎么处理?
请读者自己去研究解决。