在C语言中,double和float类型可以存储多大的数字?

6

我试图确定浮点数和double可以使用的最大数字是多少。但它并没有按照我预期的方式存储,只能存储整数值。 double应该包含8个字节的信息,足以容纳变量a,但它并没有正确地保留。它显示为1234567890123456768,其中最后2个数字不同。当我将214783648或任何数字存储在浮点变量b的最后一个数字中时,它显示相同的值214783648,这应该是极限了。到底是怎么回事?

double a;
float b;
int c;
a = 1234567890123456789;
b = 2147483648;
c = 2147483647;
printf("Bytes of double: %d\n", sizeof(double));
printf("Bytes of integer: %d\n", sizeof(int));
printf("Bytes of float: %d\n", sizeof(float));

printf("\n");

printf("You can count up to %.0f in 4 bytes\n", pow(2,32));
printf("You can count up to %.0f with + or - sign in 4 bytes\n", pow(2,31));
printf("You can count up to %.0f in 4 bytes\n", pow(2,64));
printf("You can count up to %.0f with + or - sign in in 8 bytes\n", pow(2,63));

printf("\n");

printf("double number: %.0f\n", a);
printf("floating point: %.0f\n", b);
printf("integer: %d\n", c);

return 0;

3
使用 %zu 来打印 sizeof 的输出结果。 - Sourav Ghosh
所有定义都在头文件 limits.h 中。 - barak manos
1
你可以存储无穷大!=) - Arkku
6个回答

13

浮点类型可以存储的最大有限数字是floatdouble分别对应的FLT_MAXDBL_MAX。然而,这并不意味着该类型可以精确表示每个较小的数字或整数(事实上,甚至远远不能)。

首先需要了解的是,浮点数的所有位并非“相等”的。浮点数具有一个底数(在IEEE-754标准中为8位float、11位double)和一个尾数(在floatdouble中分别为23位和52位)。该数字通过将尾数(其中包含一个隐含的前导1位和二进制点)乘以2的指数次幂(在归一化指数后;其二进制值不直接使用)获得。还有一个单独的符号位,因此以下规则也适用于负数。

随着指数的变化,尾数连续值之间的距离也会改变,即指数越大,浮点数连续可表示值之间的距离就越远。因此,你可能能够精确地存储给定大小的一个数字,但却无法存储“下一个”数字。还应记住,某些看似简单的分数无法用任何数量的二进制数字精确表示(例如1/10,十分之一,在二进制中是一个无限重复的序列,就像在十进制中的1/3,三分之一)。

至于整数,你可以精确地表示每个小于2的幂次方的整数。因此,IEEE-754的float可以表示所有小于2的24次幂的整数,而double可以表示小于2的53次幂的整数(在这些范围的后半部分,连续的浮点值恰好相差一个整数,因为整个尾数仅用于整数部分)。虽然还有一些更大的整数可以被表示,但它们之间的间隔超过了一个整数,即你只能精确表示小于等于该幅度的每个整数,而不能表示每个整数。

例如:

float f = powf(2.0f, 24.0f);
float f1 = f + 1.0f, f2 = f1 + 2.0f;
double d = pow(2.0, 53.0);
double d1 = d + 1.0, d2 = d + 2.0;
(void) printf("2**24 float = %.0f, +1 = %.0f, +2 = %.0f\n", f, f1, f2);
(void) printf("2**53 double = %.0f, +1 = %.0f, +2 = %.0f\n", d, d1, d2);

输出:

2**24 float = 16777216, +1 = 16777216, +2 = 16777218
2**53 double = 9007199254740992, +1 = 9007199254740992, +2 = 9007199254740994

如您所见,将1加到2mantissa_bits + 1上不会产生差异,因为结果是不可表示的,但添加2则会产生正确的答案(碰巧在这种情况下,可表示的数字相隔两个整数,因为乘数已经加倍)。

 

简而言之,IEEE-754 float 可以精确地表示所有小于等于 224 的整数,而double可以精确地表示所有小于等于253的整数,但只有一些更大的整数可以被准确表示(可表示值的间距取决于大小)。


很棒的答案。既然已经很长了,我会在某个地方提到,当涉及到分数时,它甚至更加违反直觉,因为即使是看似简单的数字如0.4,在十进制中也只是简单的数字,而在二进制中却不是简单的数字(并且无法精确表示)。 - Pascal Cuoq
一个简化的心理模型是想象一张网格纸,你有空间写入一定数量的数字,但你可以在任何两个数字之间放置小数点。也就是说,数字的大小由小数点的位置改变,但你仍然保持相同的精度位数。(浮点数的概念就是拥有可移动或浮动的小数点,这也是浮点数名称的来源。) - Arkku

7

sizeof(double)8,这是真的,但是double还需要一些位来存储指数部分。

假设使用IEEE-754,double最多可以精确表示2的53次方的整数,这比1234567890123456789要小。

另请参见双精度浮点格式


3
你可以使用常量来了解限制的内容:
FLT_MAX
DBL_MAX
LDBL_MAX

From CPP reference


2
请记住,这些取决于平台、操作系统、编译器和编译选项。 - rubenvb
3
这不是答案。1234567890123456789 远小于 DBL_MAX,通常为 1E+37。这个问题的真正问题不在于 double 可以存储的最大数字,而在于 double 可以精确存储多大的整数。 - Yu Hao
@YuHao:DBL_MAX通常是“1E+37”吗?打错了吗?如果我没记错的话,它应该是大约1E307左右。 - too honest for this site
2
@Olaf 我之前评论中的更正是:DBL_MAX最小可接受值是 1E+37。 它通常更大,类似于 1.79769e+308 - Yu Hao
@于豪 E+308?我有些困惑。我以前学过C语言,它似乎无法存储非常大的数字,我们只能将大数存储在数组中。你能否解释一下? - Karan Singh
@KaranSingh 你的教学是正确的,但浮点数会使事情变得复杂。如果你需要更多位数/有效数字来表示这个范围内的数字,你将无法做到。虽然e+308很大,但在这个例子中,数字只有6位!(实际上是24位左右的双精度浮点数)。这是否算作“存储一个非常大的数字”是一个哲学问题。 - Lightness Races in Orbit

0

sizeof一个对象只报告它所占用的内存空间,而不显示有效的范围。例如,可能会有一个具有2 ** 16(65536)个可能值的unsigned int在内存中占用32位。

对于浮点数对象,情况更加复杂。它们由(简化的)两个字段组成:整数尾数和指数(请参见链接文章中的详细信息)。两者都具有固定的宽度。

由于尾数仅具有有限的范围,因此会截断或四舍五入尾随位,并在需要时校正指数。这是一个原因,为什么不应该使用浮点类型来存储像货币这样精确的值。

在十进制中(注意:计算机使用二进制表示),具有4位尾数:

1000 --> 1.000e3
12345678 --> 1.234e7

你的实现参数在 float.h 中定义,类似于为整数提供参数的 limits.h


0

您可以通过打印存储在“limits.h”头文件中的限制(对于C++,相当于“std::numeric_limits”标识符,如下所示:enter link description here)来打印标准POD类型的实际限制。

由于硬件实际上不能使用浮点类型或者无法通过硬件表示浮点类型,因此硬件使用硬件的位长度来表示浮点类型。由于浮点类型没有无限长度,因此您只能为特定精度显示/呈现双变量。大多数硬件使用IEEE-754标准来表示浮点类型。

要获得更高的精度,您可以尝试使用'long double'(根据硬件,这可能比double具有四倍精度),AVX、SSE寄存器、大数库或自己编写代码。


-2
在Linux上,#include <values.h> 在Windows上,#include <float.h> 有一个相当全面的定义列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接