如何按照以下方式格式化双打
How to format doubles in the following way?
我使用的是C++,我想用以下明显的方式格式化双打。我曾尝试过使用字符串流来玩"固定"answers"科学",但我无法达到预期的输出。
double d = -5; // print "-5"
double d = 1000000000; // print "1000000000"
double d = 3.14; // print "3.14"
double d = 0.00000000001; // print "0.00000000001"
// Floating point error is acceptable:
double d = 10000000000000001; // print "10000000000000000"
根据要求,以下是我尝试过的东西:
#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
using namespace std;
string obvious_format_attempt1( double d )
{
stringstream ss;
ss.precision(15);
ss << d;
return ss.str();
}
string obvious_format_attempt2( double d )
{
stringstream ss;
ss.precision(15);
ss << fixed;
ss << d;
return ss.str();
}
int main(int argc, char *argv[])
{
cout << "Attempt #1" << endl;
cout << obvious_format_attempt1(-5) << endl;
cout << obvious_format_attempt1(1000000000) << endl;
cout << obvious_format_attempt1(3.14) << endl;
cout << obvious_format_attempt1(0.00000000001) << endl;
cout << obvious_format_attempt1(10000000000000001) << endl;
cout << endl << "Attempt #2" << endl;
cout << obvious_format_attempt2(-5) << endl;
cout << obvious_format_attempt2(1000000000) << endl;
cout << obvious_format_attempt2(3.14) << endl;
cout << obvious_format_attempt2(0.00000000001) << endl;
cout << obvious_format_attempt2(10000000000000001) << endl;
return 0;
}
打印以下内容:
Attempt #1
-5
1000000000
3.14
1e-11
1e+16
Attempt #2
-5.000000000000000
1000000000.000000000000000
3.140000000000000
0.000000000010000
10000000000000000.000000000000000
除非你写一些代码以某种方式分析数字,否则程序无法知道如何以你描述的方式格式化数字,即使这样也很困难。
这里所需要的是了解源代码中的输入格式,当编译器将十进制输入源代码转换为二进制形式存储在可执行文件中时,这种格式就会丢失。
一种可行的替代方案是输出到stringstream
,然后从中修改输出以去除尾随零。类似这样的东西:
string obvious_format_attempt2( double d )
{
stringstream ss;
ss.precision(15);
ss << fixed;
ss << d;
string res = ss.str();
// Do we have a dot?
if ((string::size_type pos = res.rfind('.')) != string::npos)
{
while(pos > 0 && (res[pos] == '0' || res[pos] == '.')
{
pos--;
}
res = res.substr(pos);
}
return res;
}
我实际上并没有厌倦它,但作为一个粗略的草图,它应该是可行的。需要注意的是,如果你有类似0.1的东西,它很可能会打印为0.0999999999285或类似的东西,因为0.1不能以精确的形式表示为二进制。
准确格式化二进制浮点数非常棘手,而且传统上是错误的。1990年在同一期刊上发表的两篇论文确定,如果转换为二进制浮点数并返回的十进制值不使用比特定约束更多的十进制数字,则可以恢复其值(在C++中,对于适当的类型T
,使用std::numeric_limits<T>::digits10
表示(:
- Clinger的"如何准确读取浮点数"描述了一种从十进制表示转换为二进制浮点的算法
- Steele/White的"如何准确打印浮点数"描述了如何从二进制浮点值转换为十进制十进制值。有趣的是,该算法甚至转换为最短这样的十进制值
在这些论文发表时,二进制浮点("%f"
、"%e"
和"%g"
(的C格式指令已经建立好,它们没有被更改为考虑新结果。这些格式化指令的规范问题在于,"%f"
假定对小数点后的数字进行计数,并且没有格式说明符要求格式化具有特定数字数的数字,但不一定从小数点开始计数(例如,格式化具有小数点但可能具有多个前导零(。
格式说明符仍然没有得到改进,例如,包括另一个用于可能涉及许多零的非科学表示法的格式说明符。实际上,斯蒂尔/怀特算法的威力并没有完全暴露出来。遗憾的是,C++格式化并没有改善这种情况,只是将语义委托给C格式化指令。
而非设置std::ios_base::fixed
并使用精度为std::numeric_limits<double>::digits10
的方法是C和C++标准库提供的浮点格式的最接近方法。请求的确切格式可以通过使用std::ios_base::scientific
格式化获得数字,解析结果,然后重写数字来获得。为了给这个进程提供一个良好的类似流的接口,它可以用std::num_put<char>
方面封装。
另一种选择是使用双重转换。此实现使用改进的(更快的(算法进行转换。它还公开了以某种形式获取数字的接口,尽管如果我记得正确的话,它不是直接作为字符序列。
您不能做您想做的事情,因为小数不能以浮点格式精确表示。换句话说,double
不能精确地保持3.14
,它将所有内容都存储为2的幂的分数,所以它将其存储为3+9175/65536或大约(在计算器上计算,你会得到3.139999389684375。(我意识到65536不是IEEE double的正确分母,但它的要点是正确的(。
这就是所谓的往返问题。你不能可靠地做
double x = 3.14;
cout << magic << x;
并获得"3.14">
如果必须解决往返问题,那么不要使用浮点。使用自定义的"decimal"类,或者使用字符串来保存值。
下面是一个可以使用的十进制类:https://stackoverflow.com/a/15320495/364818
我使用的是C++,我想用以下明显的方式格式化双打。
根据你的样品,我想你想要
- 固定的而不是科学的符号
- 合理(但不过分(的精度(这是为了用户显示,所以可以进行一点舍入(
- 尾部的零被截断,以及
- 如果数字看起来像整数,小数点也会被截断
以下功能正是这样做的:
#include <cmath>
#include <iomanip>
#include <sstream>
#include <string>
std::string fixed_precision_string (double num) {
// Magic numbers
static const int prec_limit = 14; // Change to 15 if you wish
static const double log10_fuzz = 1e-15; // In case log10 is slightly off
static const char decimal_pt = '.'; // Better: use std::locale
if (num == 0.0) {
return "0";
}
std::string result;
if (num < 0.0) {
result = '-';
num = -num;
}
int ndigs = int(std::log10(num) + log10_fuzz);
std::stringstream ss;
if (ndigs >= prec_limit) {
ss << std::fixed
<< std::setprecision(0)
<< num;
result += ss.str();
}
else {
ss << std::fixed
<< std::setprecision(prec_limit-ndigs)
<< num;
result += ss.str();
auto last_non_zero = result.find_last_not_of('0');
if (result[last_non_zero] == decimal_pt) {
result.erase(last_non_zero);
}
else if (last_non_zero+1 < result.length()) {
result.erase(last_non_zero+1);
}
}
return result;
}
如果您使用的计算机使用IEEE浮点,则不建议将prec_limit
更改为16。虽然这将使您能够正确打印0.9999999999999999,但它也将5.1打印为5.0999999999999996,将9.99999998打印为9.99999998000000001。这是从我的电脑,您的结果可能会因不同的库而有所不同。
将prec_limit
更改为15是可以的,但它仍然会导致数字无法"正确"打印。只要您不想打印1.0-1e-15,指定的值(14(就可以很好地工作。
你可以做得更好,但这可能需要放弃标准库(见Dietmar Kühl的回答(。
- 如何在c++中为模板函数实例创建快捷方式
- 在c代码之间共享数据的最佳方式
- 在C++中将函数压缩为两种方式
- C++格式化输出问题
- 以螺旋方式打印矩阵的程序.(工作不好)
- 为字符串中每 N 个字符插入空格的函数没有按照我认为的方式工作?
- 创建引用向量的优雅方式
- Constexpr替代了新的放置方式,可以让内存中的对象保持未初始化状态
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 不同/较旧的处理器运行c++代码的方式是否不同
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 如果条件为TRUE(最佳方式?),则在do while循环中后置增量
- 格式化浮点值:返回默认值
- 重载方法的方式会在使用临时调用时生成编译器错误
- 我需要一些帮助以有组织的方式格式化文件
- 以格式化的方式打开一个具有日期的文件,并获取该日期进行比较
- 如何格式化CPP方式
- 如何按照以下方式格式化双打
- 如何在c++中强制用户以格式化的方式输入
- Boost ptime:如何以浏览器在http请求的header内发送的方式格式化数据