Learning C++ 之 2.5 浮点数

整型是计算整数的好方法,但是有些时候我们会计算很大的数或者是小数。浮点数类型就可以保存实际的变量:4230.0,-3.3,0.01226。浮点数的小数点部分指出了浮点数可以位数浮动的事实:它可以支持小数点前后可变的位数的变量。

有三种不同的浮点数类型:float,double,long double。像整型一样,C++并没有具体定义这几种类型占用size的大小。在现在的架构中,浮点型表示法都是遵循IEEE 754二进制格式的。在这种格式里,float是4 bytes,double是8 bytes,long double 是8 bytes,12bytes,16bytes。

浮点型数据都是有符号的数据:

CategoryTypeMinimum SizeTypical Size
floating pointfloat4 bytes4 bytes
 double8 bytes8 bytes
 long double8 bytes8, 12, or 16 bytes

下面是一些例子:

float fValue;
double dValue;
long double dValue2;

当使用浮点类型的时候,建议至少要有一位小数位。用来区分浮点型和整型。

int x(5); // 5 means integer
double y(5.0); // 5.0 is a floating point literal (no suffix means double type by default)
float z(5.0f); // 5.0 is a floating point literal, f suffix means float type

Note:浮点型默认为double,后缀f表示float。

科学记数法:

浮点数是如何存储查出其存储范围的信息的呢?很大程度上类似于科学记数法。科学计数法是一种用简洁的文本来写出冗长的数字的方法。虽然科学记数法刚开始看起来非常陌生,但是学习掌握了科学记数法之后就能帮助你了解浮点数是如何工作的,以及它的局限性是什么。

科学记数法中数字的记录以如下的格式:significand x 10exponent。比如科学计数法中的1.2*104  1.2是有效数字,而4是指数,这也就代表着12000。

总体来说,科学记数法中的数字就是小数点左边只有一位数,剩下的在小数点右边。

如果要表示地球的质量,那么我们需要用十进制法如下表示:5973600000000000000000000 kg。这是非常大的一个数值,用8byte数据来存储都不够。而且非常难读,你搞不清有多少个0.使用科学技术表示,该重量如下:5.9736 x 1024 kg。这样更容易读。科学记数法对于非常大和非常小的数据很容易可以比较数量级,通过指数就可以了。

因为这个在C++非常难写,所以C++针对科学记数法定义如下:使用e或者E替代特殊格式,如5.9736 x 1024可以写为5.9736e24.

对于小于1的数,指数可以是负数,如5e-2,代表的就是5/100也就是0.05.

实际上我们可以用科学记数法来给浮点数赋值。

double d1(5000.0);
double d2(5e3); // another way to assign 5000
 
double d3(0.05);
double d4(5e-2); // another way to assign 0.05

怎么把数值转化成科学记数法:

用以下的步骤:

  • 指数从0开始
  • 把小数点往左移,移动到左边只有一位数,
  •   向左移动一位,你的指数+1;向右移动一位,你的指数减一
  • 去掉小数点左边的0
  • 去掉小数点右边的0,当然只有在原始数据没有小数点的时候才可以
Start with: 42030
Slide decimal left 4 spaces: 4.2030e4
No leading zeros to trim: 4.2030e4
Trim trailing zeros: 4.203e4 (4 significant digits)
Start with: 0.0078900
Slide decimal right 3 spaces: 0007.8900e-3
Trim leading zeros: 7.8900e-3
Don't trim trailing zeros: 7.8900e-3 (5 significant digits)
Start with: 600.410
Slide decimal left 2 spaces: 6.00410e2
No leading zeros to trim: 6.00410e2
Don't trim trailing zeros: 6.00410e2 (6 significant digits)

重要的是:E左边的数字称作有效数字,有效数字的位数越多,这个数的精度就越高。

精度和小数点后面的0:

考虑如下的场景,我们让两个不同的实验室测试同一个苹果的重量。一个回来说重87g,另一个回复是87.000g。假设重量是正确的,我们之前知道的苹果的真实重量在86.50~87.49之间。也许刻度只是精确到最近的g。或许我们的助理也会做一些调整。对于后一种情况,我们会更有信心确保苹果的重量有一个更高的精度。

因此,在科学记数法里我们一般都会保留一些小数位在后面,以保证数据的精确度。

然而,在C++中87和87.000是完全一样的,编译器会做同样的处理。没有技术上的原因可以让我们更喜欢哪一个表示方法。

精确度和范围:

考虑一下分数1/3,使用小数表示就是0.33333.......,3是无穷的。一个无穷的数据需要一个无穷的内存来存储,然而我们只有4byte,或者8byte的空间。浮点数可以只记录一个准确的有效位,剩下的省略掉。精度就代表了可以表示多少位有效位数字。

C++的输出函数只会保留6位的精度,也就是最多保留6位,其他的都会省略掉。

看下面的例子:

#include <iostream>
int main()
{
    float f;
    f = 9.87654321f; // f suffix means this number should be treated as a float
    std::cout << f << std::endl;
    f = 987.654321f;
    std::cout << f << std::endl;
    f = 987654.321f;
    std::cout << f << std::endl;
    f = 9876543.21f;
    std::cout << f << std::endl;
    f = 0.0000987654321f;
    std::cout << f << std::endl;
    return 0;
}

程序输出是:

9.87654
987.654
987654
9.87654e+006
9.87654e-005

你可以看到输出位数最多是6位。

需要注意的是cout有可能会转换成科学记数法显示。这个取决于编译器,指数通常会被填充到最小位数。不用害怕,9.87654e+006和9.87654e6是一样的。

但是我们是可以通过特定手段来显示更多位数的:

#include <iostream>
#include <iomanip> // for std::setprecision()
int main()
{
    std::cout << std::setprecision(16); // show 16 digits
    float f = 3.33333333333333333333333333333333333333f;
    std::cout << f << std::endl;
    double d = 3.3333333333333333333333333333333333333;
    std::cout << d << std::endl;
    return 0;
}

输出:

3.333333253860474
3.333333333333334

因为我们将数字调整位显示16位,但是你可以看到,float类型并不能精确到16位的。

浮点变量的精确度依赖于数据的size大小(float类型是小于double类型的)和存储的特殊值(有些值比其他值有更高的精度)。float数有6~9位精确度,大多数的float数据都有7位有效数字。这就是为什么我们的例子中,float表示成为16位的时候,有很多位是错误的原因。double类型的精确度可以到15~18位之间,大多数的double类型的数据都有16位。long double的精确度可以到15,18,33位精确度,这个具体取决于它所占的位数。

精度不止影响分数部分,他会影响到所有有很多位的数字。比如:

#include <iostream>
#include <iomanip> // for std::setprecision()
 
int main()
{
    float f(123456789.0f); // f has 10 significant digits
    std::cout << std::setprecision(9); // to show 9 digits in f
    std::cout << f << std::endl;
    return 0;
}

输出是:123456792

float类型一般只有7位有效的数字,但是我们需要输出9位数,这样就会导致不准确。所以一定要非常小心精确度没有数字的位数多的情况。

SizeRangePrecision
4 bytes±1.18 x 10-38 to ±3.4 x 10386-9 significant digits, typically 7
8 bytes±2.23 x 10-308 to ±1.80 x 1030815-18 significant digits, typically 16
80-bits (12 bytes)±3.36 x 10-4932 to ±1.18 x 10493218-21 significant digits
16 bytes±3.36 x 10-4932 to ±1.18 x 10493233-36 significant digits

12 byte表示的数据与16byte数据相同这个很奇怪,但是16 byte的数据拥有更高的精确度。

Note:建议优先选择double而不是float,除非空间非常珍贵的情况。因为float可能有精度不准导致数据不准的情况。

舍入误差错误:

浮点数比较复杂的一个原因是二进制存储方式和十进制存储方式的差异不明显。比如1/10,用小数表示是0.1。但是在二进制里,0.1使用如下的无限数列表示:0.00011001100110011…  因此当我们给浮点数赋值0.1的时候,会出现精度问题。

可以以下面的例子看看结果:

#include <iostream>
#include <iomanip> // for std::setprecision()
 
int main()
{
    double d(0.1);
    std::cout << d << std::endl; // use default cout precision of 6
    std::cout << std::setprecision(17);
    std::cout << d << std::endl;
    return 0;
}

输出:

0.1
0.10000000000000001

上面的输出0.1是没有问题的,但是下面的17位输出的时候,输出就不完全是0.1了。这是因为双精度数精确度有限,只能预估截断,所以我们不是完全的0.1.这就叫做舍入误差。

舍入误差可能会导致意想不到的结果:

#include <iostream>
#include <iomanip> // for std::setprecision()
 
int main()
{
    std::cout << std::setprecision(17);
 
    double d1(1.0);
    std::cout << d1 << std::endl;
	
    double d2(0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1); // should equal 1.0
    std::cout << d2 << std::endl;
}

输出:

1
0.99999999999999989

尽管我们期望两者是相等的,但是事实是不想等。如果我们使用d2在程序中,往往不能得到我们期望的结果。我们将在3.5章深入讨论这个问题。

最后一点需要注意的是舍入误差会随着数字的+或者*运算增大,比如0.1有17位的舍入误差,但是0.1加10次之后,就会成为16位的舍入误差。

NaN & Inf:

浮点数有两个特别的类型,一个是Inf,代表无穷大;一个是NaN,代表是“非数字”。当然有很多类型的NaN,这里不做仔细讨论。

下面是一个程序:

#include <iostream>
 
int main()
{
    double zero = 0.0;
    double posinf = 5.0 / zero; // positive infinity
    std::cout << posinf << std::endl;
 
    double neginf = -5.0 / zero; // negative infinity
    std::cout << neginf << std::endl;
 
    double nan = zero / zero; // not a number (mathematically invalid)
    std::cout << nan << std::endl;
 
    return 0;
}

下面输出:

1.#INF
-1.#INF
1.#IND

结论:

关于浮点数有两件事情你需要掌握

1)float数字可以存储非常大或者这非常小的数字,只要能够保证数字的精度

2)浮点数通常由小的舍入误差,及时数字在精度范围以内。很多时候因为这个非常小,或者由输出器给截断了,所以我们体验不到。因此直接对浮点数进行比较会不准确。而且随着计算会导致舍入误差增大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值