C++精度控制 - STEMHA's Blog

C++精度控制

C++的格式控制语法

ostream 类是从ios派生而来的,而ios则是从ios_base派生来的。 ios_base类封装了C++标准中的流输入输出中不依赖于读写的数据的类型的基本信息,如格式化信息、异常状态、事件回调函数等,所以很多的格式控制都需要。

  • C++程序设计时,一般不会直接调用std::ios_base的成员函数,但是会经常用到该类中定义的各种流的数据格式的枚举值常量。如ios_base::hex、ios_base::skipws等等。
  • C++中通过cout来实现格式输出,就类似于C语言中通过printf()来实现格式输出。
  • cout.setf()的作用是通过设置格式标志来控制输出形式

保留有效位数

setprecision(n):控制浮点数显示的有效数字个数,这个有效数字是囊括了小数点之前的整数部分的,比如12.345是五位有效数字。
特性:

  • 四舍五入进行保留;
  • 如果有效位数不够,不会自动补0(3.15,setprecision(4);3.15)
  • 如果小数点前的位数多余要保留的位数,则使用科学计数法

代码实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double num=12.3456789;//小数点后有7位,一共是9个有效数字
cout<<setprecision(1)<<"setprecision(1): "<<num<<endl;
cout<<setprecision(4)<<"setprecision(4): "<<num<<endl;
cout<<setprecision(6)<<"setprecision(6): "<<num<<endl;
cout<<setprecision(8)<<"setprecision(8): "<<num<<endl;
cout<<setprecision(10)<<"setprecision(10): "<<num<<endl;
return 0;
}

说明:使用cout<<setprecision(n)时#include <iomanip>在这里必须要带上,否则会出现如下错误。

1
2
3
4
main.cpp: In function ‘int main()’:
main.cpp:6:8: error: ‘setprecision’ was not declared in this scope
6 | cout<<setprecision(1)<<"setprecision(1): "<<num<<endl;
| ^~~~~~~~~~~~

运行结果:

1
2
3
4
5
setprecision(1): 1e+01        //小数点前的位数多余要保留的位数,使用科学计数法
setprecision(4): 12.35
setprecision(6): 12.3457
setprecision(8): 12.345679 //可以看到这里是四舍五入保留有效数字的
setprecision(10): 12.3456789 //有效位数不够,不会自动补0

当然也可以用cout.precision(n);这样的写法,这个写法不用加#include <iomanip>头文件。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;
int main()
{
double num=12.3456789;//小数点后有7位,一共是9个有效数字
cout.precision(1);
cout<<"setprecision(1): "<<num<<endl;
//输出的结果是setprecision(1): 1e+01
return 0;
}

保留小数点后n位

fixedsetprecision(n)结合可以实现保留小数点后几位,且会补0。//fixed指定点,即小数点后有n个有效数字
特性:

  • 保留小数点之后的几位有效数字,不包括整数部分。
  • 四舍五入进行保留
  • 如果小数点之后有效位数不够,自动补0

写法一般有三种:

1
2
3
4
5
6
7
8
/*写法1*/
cout << setiosflags(ios::fixed) << setprecision(n);
/*写法2*/
cout<<fixed<<setprecision(n);
/*写法3*/
cout.flags(cout.fixed) //定点,即小数点后有n个有效数字

cout.unsetf(cout.fixed) //取消定点法,即变成输出n个有效数字
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double a = 12.3456789;
cout<<fixed<<setprecision(4)<<a<<endl;
cout<<fixed<<setprecision(8)<<a<<endl;
cout<<fixed<<setprecision(10)<<a<<endl;
return 0;
}

结果:

1
2
3
12.3457
12.34567890
12.3456789000

在计算过程中保留有效位数。

要对 sum 保留6位有效数字,需要在计算过程中就直接转换成 double(一般不用float)

1
2
sum += (double)(1.0/i);
cout << fixed << setprecision(6) << sum << endl;

使用数学公式来实现四舍五入

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
double a = 13.456789;
double b;
b = floor(a * 10000.000f + 0.5) / 10000.000f; /*保留小数点后四位*/
cout<<b; //输出13.4568
return 0;
}

设置浮点数的显示精度

这个各种在线编程题中经常会考到的,尽量记住啊!也就是显示几位有效数字。

std::ios_base::precision是显示有效位的成员函数
原型如下:

1
2
3
streamsize precision() const;          (1)
streamsize precision( streamsize new_precision ); (2)
`

上述函数管理 std::num_put::do_put 所进行的浮点输出精度(即生成多少数位)。
(1) 返回当前精度。
(2) 设置精度为给定值。

std::basic_ios::init 所建立的默认精度为 6
想要恢复只有通过调用精度控制 cout.precision(6);

其中new_precision可以是const也可以是#define宏定义过的。

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
int main()
{
float price1=10.45,price2=1.5+5.0/6.0;
cout<<"price1 = "<<price1<<endl; //正常格式
cout<<"price2 = "<<price2<<endl; //正常格式,默认6位有效
cout.precision(4); //显示4位精度
cout<<"price1 = "<<price1<<endl; //只有4位有效,出现0会自动省略
cout<<"price2 = "<<price2<<endl; //只有4位,不会四舍五入
}

结果:

1
2
3
4
price1 = 10.45
price2 = 2.33333
price1 = 10.45
price2 = 2.333

打印末尾的0和小数点

ios_base提供了setf函数(用于set标记),能够控制多种格式化特性,此外ios_base函数提供了多个常量,可做setf函数的参数个数
如下面一行代码使cout显示末尾小数点,当然,也会显示末尾位0的

1
cout.setf(ios_base::showpoint);

测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;
int main()
{
float price1=10.40,price2=1.5+5.0/6.0;
cout<<"pricel = "<<price1<<endl; //正常格式
cout<<"price2 = "<<price2<<endl; //正常格式
cout.setf(ios_base::showpoint); //显示小数点和后面的0
cout<<"pricel = "<<price1<<endl; //不会忽略0
cout<<"price2 = "<<price2<<endl;
}

结果:

1
2
3
4
pricel = 10.4
price2 = 2.33333
pricel = 10.4000
price2 = 2.33333

setf其他方法

setf有两种原型:

1
2
fmtflags setf(fmtlags); (1)
fmtflags setf(fmtflags,fmtflags); (2)

先来讲第一种:
fmtflagsbitmask类型的typedef名,用于存储格式标记,

  • 该名称还是在ios_base类中定义的
  • bitmask类型是一种用来存储各个位置的类型,可以是整型,枚举,甚至可以说是STL bitset容器

下表是fmtflag setf(fmtlags)的一些常量控制和格式控制

常量 含义
ios_base::boolapha 输出和输入bol值
ios_base::showpoint 显示末尾小数点
ios_base::uppercase 大写字母输出16进制
ios_base:showbase 输出C++基数前缀
ios_base::showpos 正数前面加上’+’号

测试其中几个函数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
int main()
{
int s=20;
cout.setf(ios_base::showpos);
cout<<"s = "<<s<<endl; //已经填充'+'号
cout<<hex<<"s = "<<s<<endl; //16进制输出
cout.setf(ios_base::uppercase);
cout.setf(ios_base::showbase);
cout<<"s = "<<s<<endl; //以16进制输出,并输出前缀Ox
}

结果

1
2
3
s = +20
s = 14
s = 0X14

下面介绍第二种:

1
fmtlags setf(fmtflags,fmtflas);

和第一种不同的是,这种格式会返回以前的设置,第二种第一个参数指出要设置哪些位,第二个参数指出要清除哪些位。
下面函数调用和16进制控制符作用相同

1
cout.setf(ios_base::hex,ios_base::basefield);
第一参数 第二参数 含义
ios_base::dec ios_base::basefield 使用十进制基数
ios_base::oct ios_base::basefield 使用八进制基数
ios_base::hex ios_base::basefield 使用16进制基数
第一参数 第二参数 含义
ios_base::fixed ios_base::floatfield 定点计数法
ios_base::scientifc ios_base::floatfield 使用科学计数法
ios_base::left ios_base::adjustfield 使用左对齐
ios_base::right ios_base::adjustfield 使用右对齐
ios_base::internal ios_base::adjustfield 符号或者基数前缀左对齐,值右对齐

标准控制符表

可以通过使用下面的操作符,不直接操作标志。
例如:当我们设置dec标志时, 我们可以使用下面的命令:

1
cout << dec; //设置dec表示

设置endl标志时,可以使用下面命令:

1
cout << endl; //输出换行标示,并清空缓冲区

iostream中定义的操作符

操作符 描述 输入 输出
boolalpha 启用boolalpha标志
dec 启用dec标志
endl 输出换行标示,并清空缓冲区
ends 输出空字符
fixed 启用fixed标志
flush 清空流
hex 启用 hex 标志
internal 启用 internal标志
left 启用 left标志
noboolalpha 关闭boolalpha 标志
noshowbase 关闭showbase 标志
noshowpoint 关闭showpoint 标志
noshowpos 关闭showpos 标志
noskipws 关闭skipws 标志
nounitbuf 关闭unitbuf 标志
nouppercase 关闭uppercase 标志
oct 启用 oct 标志
right 启用 right 标志
scientific 启用 scientific 标志
showbase 启用 showbase 标志
showpoint 启用 showpoint 标志
showpos 启用 showpos 标志
skipws 启用 skipws 标志
unitbuf 启用 unitbuf 标志
uppercase 启用 uppercase 标志
ws 跳过所有前导空白字符

iomanip中定义的操作符

操作符 描述 输入 输出
resetiosflags(long f) 关闭被指定为f的标志
setbase(int base) 设置数值的基本数为base
setfill(int ch) 设置填充字符为ch
setiosflags(long f) 启用指定为f的标志
setprecision(int p) 设置数值的精度(四舍五入)
setw(int w) 设置域宽度为w

如果系统支持则可以使用,否则继续使用setf();

进制转换

1.将进制进行转换
ios_base类作为引导例如,控制整数以十进制,十六进制,八进制显示,

可以使用dec,hex,oct控制符,如下:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main()
{
int a=10;
cout<<"original:"<<a<<endl; //原型输出
cout<<"dec:"<<dec<<a<<endl; //十进制输出a
cout<<"hex:"<<hex<<a<<endl; //十六进制输出a
cout<<"oct:"<<oct<<a<<endl; //八进制输出a
}

输出结果如下

1
2
3
4
original:10
dec:10
hex:a
oct:12

注意:使用dec,hex,oct进行格式控制的时候。一旦进行了格式控制,其格式控制的时间会在接下来程序执行的时间内持续。

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main()
{
int b=100,a=10;
cout<<hex<<"a= "<<a<<endl; //十六进制输出a
cout<<"b = "<<b<<endl;
return 0;
}

输出的结果是

1
2
a= a
b = 64

修改字符宽度-width()函数

width()可以修改字符的宽度

1
2
int width();
int width(int i);

width(i)中,i是宽度,当然width只能影响下一个项目,然后字符宽度将自动恢复为默认值。例子如下代码:

1
2
3
cout<<'#';
cout.width(10);
cout<<10<<'#'<<50<<"#\n";

结果

1
#        10#50#

widt(i),i的参数可以是const 类型,也可以是#define类型

填充字符-fill()函数

fill()成员函数可以用来改变填充字符,例如:cout.fill(’@’)

代码如下:

1
2
3
4
5
6
7
8
9
cout.fill('@');                  //填充@字符
char *str[3]={"这里识别不出来","有几个","号"};
int num[3]={500,600,700};
for(int i=0;i<=2;i++) //3次循环
{
cout<<str[i]<<" :$"; //输出str的每个元素
cout.width(5);
cout<<num[i]<<endl; //输出num的每个元素
}

运行结果如下:

1
2
3
这里识别不出来 :$@@500
有几个 :$@@600
号 :$@@700

取消c++ setprecision 设置的精度

C++11需要:

1
std::cout << std::defaultfloat;

C++11之前需要:

1
std::cout.unsetf(std::ios_base::floatfield);

两种方法都可以把浮点输出格式还原为默认状态。

参考资料

C++精度控制说明(详细)
C++ 中的 cout.setf() 函数
C++保留任意小数点位数&格式化输出

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×