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位

fixed和setprecision(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)

先来讲第一种:
fmtflags是bitmask类型的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

×