C语言——数据存储

一、整型数据的存储

整型数据是以二进制补码形式在内存中存储的。

1、以补码形式存储

#include <stdio.h>

int main()
{
	int a = 10;
	//00000000 00000000 00000000 00001010 原码
	//00000000 00000000 00000000 00001010 反码
	//00000000 00000000 00000000 00001010 补码
	//0x00 00 00 0a 十六进制

	int b = -10;
	//10000000 00000000 00000000 00001010 原码
	//0x80 00 00 0a 十六进制
	//11111111 11111111 11111111 11110101 反码
	//0xff ff ff f5 十六进制
	//11111111 11111111 11111111 11110110 反码
	//0xff ff ff f6 十六进制
	return 0;
}

对a的内存中进行监视,我们并不能发现整型数据是以补码形式存储,因为正数的原码、反码、补码都一样,我们只能通过对b的内存中的数据进行监视才能发现整型数据在内存中是以补码形式存储。

通过这里的数据我们发现整型数据在内存中是以二进制补码形式存储的,这里的十六进制是方便观察,4字节二进制序列太长。

那为什么是以补码形式存储的呢?

这是因为:

补码的形式便于运算

cpu只有加法器,没有减法器,所以计算机计算减法时,是把减法转化为加法。

例如要计算5 - 5,要转化为5 + (-5)来计算

1)用源码计算:0000 0101 + 1000 0101 = 10001010 = -10(D)        (结果不对)

2)用反码计算:0000 0101 + 1111 1010 = 1111 1111

1111 1111为反码,转换成原码为1000 0000

1000 0000 = -0

这样0就有了两种表达方式:1000 0000    (-0)D    和    0000 0000    (+0)D

这样减少了可表示数字的数量

3)用补码计算:0000 0101 + 1111 1011 = 1 0000 0000(超出第七位。高位溢出,即第八位舍去),得出结果为0,正确。

而且补码形式0只有一种表达方式,相比反码增加了可表示数字的数量

而且我们发现这里的十六进制序列是倒置的,这是为什么呢?

这里是另一个原因——大小端字节序。

2、大小端字节序

在存储时,整数的每个字节会按照特定的顺序放置在内存中,这个顺序称为字节序或端序。端序分为两种:

  1. 大端字节序(Big-Endian):在大端字节序中,一个多字节数据的最高有效字节(即“大端”)存储在内存的最低地址处,其余字节按照在数值中的顺序依中次存储在连续的内存地址。例如,一个四字节的整数 0x12345678 在内存中的存储顺序(从低地址到高地址)为 12 34 56 78

  2. 小端字节序(Little-Endian):在小端字节序中,一个多字节数据的最低有效字节(即“小端”)存储在内存的最低地址处,其余字节按照在数值中的逆序存储在连续的内存地址中。采用同样的四字节整数 0x12345678 为例,在内存中的存储顺序(从低地址到高地址)将会是 78 56 34 12

例如,假设我们有一个类型为int的变量,值为0x12345678,它在内存中的存储方式会因字节序而异:

  • 在大端序系统中,它会这样存储:12 34 56 78(地址从低到高)
  • 在小端序系统中,它会这样存储:78 56 34 12(地址从低到高)

在上面的程序中我们发现变量a在内存中的存储是:

低地址  0a 00 00 00 高地址

在上面的程序中我们发现变量b在内存中的存储是:

低地址 f6 ff ff ff 高地址

这就是小端字节序。

大小端字节序在我之前的文章《C语言——小细节和小知识9》中有详细介绍。大小端是由硬件设计决定的。

3、例子

设计一个程序,判断机器的字节序是大端字节序还是小端字节序。

#include <stdio.h>

int main()
{
	int a = 1;
	if (*(char*)&a == 1)
	{
		printf("小端字节序\n");
	}
	else
	{
		printf("大端字节序\n");
	}
	return 0;
}

通过大小端字节序的特点:

对比两种字节序的存储情况,我们发现两个数据的首字节中的数据是不同的。我们可以用将指针强制转换类型的方法来只访问第一个字节的内容。再将第一个字节的内容与1比较,如果为1,则为小端字节序,如果不是1,则为大端字节序。这样就能实现判断机器的字节序。

运行结果:

4、对于补码的补充

我们知道原码转化为补码的方法是是按位取反后加一,就如十进制有符整型数据-5的二进制原码序列是:

10000000 00000000 00000000 00000101

将它转换为补码形式为:

11111111 11111111 11111111 11111011

具体步骤:

从这里,我们可以知道,如果我们想将补码转换为原码的话,我们就可以通过这样的步骤:

先将补码减一,然后补码转换为反码,然后将反码按位取反,就得到了原码。

具体步骤:

这样就可以将补码转换为原码。

注意:这里的补码和原码转换是负数的,正数的原码、反码和补码是相同的。

实际上将补码转换为原码还有另外一种方法:

直接将补码按位取反后加一,就能得到原码。

这实际上表示了补码的补码就是原码。

具体步骤:

对于补码和原码相互转换,其运算过程是相同的,这样不需要额外的硬件电路。

5、一些实例

例1、无限循环

#include <stdio.h>

int main()
{
	unsigned int i = 0;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

这段代码的运行结果实际上是一个无限循环输出数字的。

在变量 i 递减到0后,因为条件是i >= 0,所以 i 为0依旧可以进入循环,执行完 for 循环中的语句后,在进行 i-- 语句,这时变量 i 变成了-1,而-1的补码是11111111 11111111 11111111 11111111,然而这里的变量 i 是 unsigned int 类型,unsigned int 类型是大于0的,所以这里的-1会被识别成正数,而11111111 11111111 11111111 11111111作为一个正数的补码,因为正数的补码和原码是一样的,所以这里的正数是2^32 - 1,即为4294967295,这时会继续递减直到变量 i 又递减到0,又变成了2^32 - 1,然后就这样进入无限循环。

实际上单从 unsigned int 的范围和循环条件来看的话,这段循环也是无限循环,因为 unsigned int 是一定大于零的,而循环条件又是大于等于零,实际上这里的变量 i 是永远不会小于零的,所以这是一个无限循环。

例2、计算字符串长度

#include <stdio.h>
#include <string.h>

int main()
{
	char arr[1000];
	int i = 0;
	for (i = 0; i < 1000; i++)
	{
		arr[i] = -1 - i;
	}
	printf("%zu\n", strlen(arr));
	return 0;
}

这里的输出结果是:

因为strlen函数是找 \0 ,只要找到 \0 就会返回 \0 前面的字符数。这里的数组是char类型,char类型的范围是-128 ~ 127,这里循环中是从-1一直递减,递减到-1001。实际上-1001超出了范围,所以要具体分析。从这里的-1一直递减,知道递减到范围最小值-128,因为对于char类型,-128的补码是1000 0000,这时循环继续进行,接着递减,就变成了0111 1111,这是127的补码,然后接着递减,直到递减到0。

这里的输出结果就是在0之前的符号个数,

-1 -2 -3 …… -128 127 126 …… 3 2 1 0,不算上0总共有255个字符,所以输出结果是255。

例3、无限循环打印

#include <stdio.h>

int main()
{
	unsigned char i = 0;
	for (i = 0; i <= 255; i++)
	{
		printf("Hello World!\n");
	}
	return 0;
}

这里unsigned char变量类型的范围是0 ~ 255,在循环控制变量 i 增加到255时,会进行循环,然后 i 自增,变成256,但是256的二进制补码是1 00000000,而unsigned char类型的大小是一字节,所以 i 中的数据是00000000,即为0,然后就进入了无限循环。

例4、strlen返回值

#include <stdio.h>
#include <string.h>

int main()
{
	if (strlen("abc") - strlen("abcdef") >= 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<\n");
	}
}

我们知道strlen会返回字符串长度,所以这段代码中 if 中的判断语句应当时3 - 6 >= 0 ,3 - 6 = -3,不大于0,所以应该时打印 < ,但是实际不是这样的。
这是因为strlen返回的值是size_t类型的数据,而size_t类型实际上是unsigned int类型,所以不可能小于零,所以这里的运行结果是:

二、浮点型数据的存储

一些浮点数:

3.14159

1E10        就是1.0 * 10 ^ 10

1、浮点型数据的存储

在IEEE标准的二进制浮点数表示(如单精度浮点数IEEE 754-1985标准)中,一个数的表示形式通常是这样的:

(-1)^sign × 1.mantissa × 2^(exponent - bias)
  1. 数符(Sign):这是一个位标志,用来表示数值的正负。在二进制浮点数表示中,通常是一个位,0代表正数,1代表负数。

  2. 阶码(Exponent):用于表示数值的范围或者大小级别。在浮点数表示中,阶码确定了浮点数的基数(通常是2)的指数。阶码通常是以偏移量(或称为excess)的形式存储的,这样就可以表示正指数和负指数。

    在浮点数的表示中,偏移量(bias)是用在阶码(exponent)部分的一个值,其目的是允许表示负指数的能力而无需使用符号。偏移量是一个预先定义的数值,根据浮点数的位数和标准来确定。

    IEEE 754标准定义了单精度(32位)和双精度(64位)浮点数的格式。在这两种格式中,偏移量的计算方式如下:
    对于单精度浮点数:阶码部分是8位,所以偏移量是 2^(8-1) - 1,即 127。
    对于双精度浮点数:阶码部分是11位,所以偏移量是 2^(11-1) - 1,即 1023。

    使用偏移量后,阶码存储的数值是实际指数加上偏移量。例如,在单精度浮点数中,如果想要表示指数 -3,实际存储的阶码将是 -3 + 127 = 124。同样,要表示指数 +4,存储的阶码将是 4 + 127 = 131。这种方式能够让我们不使用单独的符号位,仅用一个无符号整数来表示可能为负的指数值。

    当浮点数被解码时,解码过程中会从阶码中减去这个偏移量,以得到实际的指数值。这个过程是编码过程的逆过程。例如,如果从内存中读取到的阶码是 124,在单精度浮点格式中,解码后的实际指数是 124 - 127 = -3。

    偏移量的使用是对浮点数表示的一个重要细节,使得浮点数能够以一种统一的格式来表示非常广泛的数值范围。

  3. 尾数(Mantissa):又称为有效数字,它是浮点数的小数部分,用于表示数值的精度。在标准化的浮点数表示中,尾数前通常有一个隐含的1(对于二进制浮点数),这意味着实际的数值是1.xxxxxx的形式,其中xxxxxx代表尾数部分。在浮点数的表示中,1.xxxxxx指的是浮点数中尾数(mantissa)的规格化形式。规格化的浮点数具有以下特点:
    对于二进制来说,规格化的形式意味着尾数的最高位(最左边的位)总是1。由于这个1是固定的,它实际上不需要在内存中表示出来,因为我们总是知道它存在。因此,我们可以在尾数中存储更多的有效数字,这提升了表示的精度。
    计算机中的浮点数通常按照IEEE 754标准来表示。在这个标准下,尾数部分通常是一个介于1(含)和2(不含)之间的二进制小数。例如,二进制数1.101代表十进制中的1 + 0.5 + 0.125 = 1.625
    1.xxxxxx中的1是隐含的整数部分,而.后面跟着的.xxxxxx是实际存储在浮点数结构中的小数部分。
    以IEEE 754单精度浮点数为例,这种格式有一个1位的符号位、8位的指数(阶码),以及23位的尾数(小数部分),加一起刚好4个字节。由于规格化的尾数隐含了最高位的1,所以实际上你有24位的精度。这意味着如果你有一个规格化的浮点数,它的尾数部分是10110100000000000000000,那么实际表示的值是1.10110100000000000000000,即包括隐含的最高位的1。

float类型存储模型:

有一个1位的符号位、8位的指数(阶码),以及23位的尾数(小数部分),加一起刚好4个字节。

double类型存储模型:

有一个1位的符号位、11位的指数(阶码),以及52位的尾数(小数部分),加一起刚好8个字节。

2、十进制小数转换为二进制小数

整数部分:

按十进制整数转换为二进制整数计算。

十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2去除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为零时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。

小数部分:

十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又得到一个积,再将积的整数部分取出,如此进行,直到积中的小数部分为零(即积为0或1),或者达到所要求的精度为止。

然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位,即为顺序排列。

例、将十进制小数9.6转换为二进制

首先是整数部分的转换。将十进制整数9转换为二进制:

9 / 2 = 4 余 1

4 / 2 = 2 余 0

2 / 2 = 1 余 0

1 / 2 = 0 余 1

然后将得到的余数反向排列,得到整数部分的二进制表示为1001。

接下来是小数部分的转换。将十进制小数0.6转换为二进制:

0.6 × 2 = 1.2 → 记下整数部分 1

0.2 × 2 = 0.4 → 记下整数部分 0

0.4 × 2 = 0.8 → 记下整数部分 0

0.8 × 2 = 1.6 → 记下整数部分 1

0.6 × 2 = 1.2 → 循环开始重复(因此,小数部分将被一个循环的二进制表示表示)

将每次乘法得到的整数部分排列起来,得到小数部分的二进制表示为1001(1001...),其中括号表示循环。

所以,十进制浮点数9.6的二进制近似表示为 1001.1001(1001...)。由于二进制无法精确表示大多数十进制小数,这个转换通常会有一个重复的循环部分或者需要截断到一定的位数。

3、浮点数转换为数符+阶码+尾数形式

例1、将5.0这个十进制浮点数转化为二进制形式:

  1. 符号位(Sign bit)5.0 是正数,所以符号位是 0

  2. 将数值转换为二进制形式: 将 5.0 转换成二进制数是 101.0

  3. 规格化数值: 将 101.0 转换为二进制科学记数法,即将小数点左移两位,得到 1.01,并记下小数点移动的位数(这里是2位),即指数为 2

  4. 计算阶码(Exponent): 根据IEEE 754单精度格式,阶码字段是8位,使用127作为偏移量(bias)。实际指数为 2,加上偏移量得到 2 + 127 = 129。转换为二进制,129 的二进制表示是 10000001

  5. 计算尾数(Mantissa): 将小数点后的部分(01)放到尾数字段。尾数字段为23位,剩余的位用0填充。所以二进制尾数字段为:0100000...0(一直到23位)。

将上面的三个部分组合起来,就得到了 5.0 的IEEE 754单精度浮点数表示:

  • 符号位:0
  • 阶码位:10000001
  • 尾数位:0100000...0 (23位)

因此,5.0 的单精度浮点数二进制表示是 0 10000001 0100000...0,合并起来是:

0 10000001 01000000000000000000000

4、大小端字节序

对于十进制小数5.5转化为二进制的形式:

#include <stdio.h>

int main()
{
	float f = 5.5;
	// 数符位是0
	// 5.5转换为二进制为101.1,科学计数法为1.011*2^2
	// 所以阶码是2,加上偏移量127为129,二进制序列是1000 0001
	// 转换为二进制为
	// 0 10000001 01100000000000000000000
	// 0100 0000 1011 0000 0000 0000 0000 0000
	// 十六进制为
	// 0x40 b0 00 00
	return 0;
}

最后的十六进制序列是:

0x40 b0 00 00

方便我们在调试时观察。

进行调试时,我们发现:

这里的顺序时颠倒的,实际上这里是小端字节序。将小端放到了低地址处。

我之前的文章《C语言——小细节和小知识9-CSDN博客》中详细介绍了大小端字节序。

5、对阶码(Exponent)的补充

我们知道阶码是一个无符整数,且是以偏移量的形式表示的,所以在解码阶码时我们是用阶码减去偏移量得到真实的指数,这里有三种情况:

1、阶码不全是1或不全为0时(规格化数)

这时,直接将阶码减去127(或1023)得到指数,因为尾数有一个隐含的前导1,然后需要将将尾数的1加上变成1.xxxxxx形式。

例如:0 10000001 01100000000000000000000

数符位为0,(-1)^0 = 1 为正数

阶码为129,129 - 127 = 2,指数为2

尾数为1.01100000000000000000000

1.011*2^2 = 101.1

转换为十进制为5.5

2、阶码全为0(非规格化数)

对于阶码全为0的情况,这在IEEE 754标准中被用来表示两种特殊的数:

  1. 零(Zero):当阶码和尾数都为0时,根据数符位的不同可以表示+0或-0。
  2. 非规格化数(Denormalized Numbers/Subnormal Numbers):当阶码全为0但尾数不为0时,这时尾数不会加上最前面的1,而是在最前面加上0,变成0.xxxxxx形式。这种情况被用来表示非常靠近0的数。非规格化数允许表示的数范围扩展到比最小的规格化数(正)还小的值,但是这些数不具有规格化数的精度。

对于非规格化数,其值计算公式为:(-1)^数符位 * 2^(1-偏移量) * 0.尾数部分,所以非规格化数的指数恒为-126(或-1022)。

3、阶码全为1(特殊值)

当一个浮点数的阶码部分全为1时,在IEEE 754标准下它表示两种特殊的值:

  1. 无穷大(Infinity):如果尾数部分全为0,这表示无穷大。数符位决定了是正无穷(+∞)还是负无穷(-∞)。

  2. NaN(Not a Number,非数):如果尾数部分不全为0,这表示一个非数值(NaN)。NaN用于表示某些未定义的或不可表示的数学运算结果,例如0除以0,或者负数的平方根等。

因此,对于单精度浮点数:

  • 规格化数的指数范围是从 ( -126 ) 到 ( +127 ) (二进制阶码表示从 00000001 到 11111110)。
  • 非规格化数的指数固定为 ( 1-127 = -126 )。
偏移量阶码范围(真值表示)阶码范围(偏移量表示)二进制阶码表示
127[-126,127][1,254]0000 0001到1111 1110

对于双精度浮点数:

  • 规格化数的指数范围是从 ( -1022 ) 到 ( +1023 ) (二进制阶码表示从 00000001 到 11111110)。
  • 非规格化数的指数固定为 ( 1-1023 = -1022 )。
偏移量阶码范围(真值表示)阶码范围(偏移量表示)二进制阶码表示
1023[-1022,1023][1,2046]000 0000 0001到111 1111 1110

6、对于非规格化数(Denormalized Numbers/Subnormal Numbers)的补充

1、为什么增加非规格化数

这里以单精度浮点数float的正数部分举例:

因为非规格化数的尾数部分时0.xxxxxx,指数又被定义为-126,所以实际上可以表达的指数是小于或等于-127的。

因为非规格化数的尾数部分有若干前导0组成,即为0.000…0001…,因为它已经超出了规格化形式的范围(最小规格化数为1*2^-126)。对于非规格化数,一个数越小,它的前导0越多,否则指数部分就不符合要求,这里的要求就是非规格化数的指数是固定为(1 - 偏移量)的。所以,非规格化数的指数实际上比最小的规格化数(最小规格化数的指数为1 - 127 = -126)的指数还要小。

例如有一个浮点数1.011*2^-129,很明显,这个数已经超出了最小规格数的范围,如果要存储它,就要转换成非规格化数,即为0.001011*2^-126,这里尾数有多个前导零,如果浮点数更小的话,就需要更多个前导零。

我们不难得出,最小的非规格化数的尾数部分是2^-23,所以最小规格化数为2^-149。

最小规格化数为2^-126,对于普通计算,这个数已经够小了,但对于科学计算,可能还不够。二最小非规格化数比最小规格化数小了23个数量级,更接近零。

这里使用float类型的正数部分举例,负数部分类比正数部分。

2、为什么非规格化数的指数定为(1-偏移量)

这里依旧以单精度浮点数float的正数部分举例:

对于非规格化数的指数,是恒为-126的。然而非规格化数的判定又是阶码部分全为0,也就是0000 0000,这时阶码转换为十进制为0,那为什么非规格化数的指数不像规格化数那样定为(阶码 - 127)呢,这样非规格化数的最小值就又小了一个量级了啊。

这是因为,如果还是用(阶码 - 127)的形式的话,最大非规格化数和最小规格化数之间会有空隙,如果改为(1 - 偏移量)的话,情况就不一样了,因为(1 - 偏移量)的大小也是最小规格化数的指数大小。这样的话,当非规格化数的尾数为最大值(即0.11111111111111111111111或1-2^-23)时,就非常接近最小规格化数,这时从规格化数到非规格化数会非常平滑,中间几乎没有间隙。

这里使用float类型的正数部分举例,负数部分与正数部分相似。

7、类型转换时的精度损失和溢出

1、精度损失

int 转换到 float 可能会有精度损失,因为 float 的尾数部分只有 24 位有效位,也就是精度最大是24位,而 int 是 32 位。如果要转换的 int 类型数据的有效位数超过 24,超出的部分会被舍弃,导致精度损失。

double 转换到 float 时,由于 double 有 53 位有效位数,超过 float 的 24 位,因此也会有精度损失。

double 转换到 int 时,由于 double 有 53 位有效位数,int 是 32 位,因此也会有精度损失。

浮点数转换到整数时,若有小数部分,小数部分会被丢弃,造成精度损失。

2、溢出

正下溢(Positive Underflow):

  • 当计算结果在正数范围内,但小于数据类型可表示的最小正数时发生。
  • 在浮点数系统中,如果结果小于IEEE标准定义的最小正浮点数,可能返回一个接近于正零的数,但不是精确的零。

正上溢(Positive Overflow):

  • 当计算结果超过数据类型可表示的最大正数时发生。
  • 在浮点数系统中,超过IEEE标准定义的最大正浮点数,可能返回正无穷大。

负下溢(Negative Underflow):

  • 当计算结果在负数范围内,但大于数据类型可表示的最大负数时发生。
  • 在浮点数系统中,如果结果大于IEEE标准定义的最大负浮点数,可能返回一个接近于负零的数,但不是精确的零。

负上溢(Negative Overflow):

  • 当计算结果超过数据类型可表示的最小负数时发生。
  • 在浮点数系统中,超过IEEE标准定义的最小负浮点数,可能返回负无穷大。

当 float 或 double 转换为 int 时,如果数值超出 int 类型的范围(-2^31 到 2^31 - 1),就会发生溢出。此时的行为可能是未定义的,取决于编程语言和编译器如何处理溢出。

double 转换到 float 或 intfloat 转换到 int 也可能溢出。如果数值超出转换的目标类型的范围,就会发生溢出。

8、舍入模式

浮点数的尾数部分精度有限,无法表示过长的数字。因此,在将浮点数存储在内存中时,必须将多余的位舍去并选择一个近似值。为了确定这个近似值,IEEE 754制定了四种不同的舍入模式。

1、最近舍入模式

当浮点数进行舍入时,最接近舍入模式会选择与原始数值最接近的可表示值。具体而言,如果有两个可表示的值与原始数值距离相等,那么最接近舍入模式会选择其中的偶数。

例如,假设我们有一个浮点数0.5进行舍入。根据最接近舍入模式,我们将选择与0.5最接近且可表示的值。由于0.5距离0和1都相等,但是根据最接近舍入模式的规定,我们会选择偶数值。因此,0.5会被舍入为0。

同样地,如果我们有一个浮点数1.5进行舍入,根据最接近舍入模式,我们会选择与1.5最接近且可表示的值。由于1.5距离1和2都相等,但是根据最接近舍入模式的规定,我们会选择偶数值。因此,1.5会被舍入为2。

最接近舍入模式确保了在舍入过程中的一致性和可预测性,同时也尽可能地减少了舍入带来的误差。这是最常用的舍入模式,也是IEEE 754标准的默认舍入模式。

2、向上舍入

当进行向+∞方向舍入(向上舍入)时,结果会被舍入为离它最近的较大值,也就是朝正无穷大的方向靠近。这意味着小数部分会被舍入为0,而整数部分会被增加到下一个整数。这种舍入模式通常由标准库函数ceil()使用。

举个例子,如果我们对1.324进行向上舍入,结果会变为2。同样地,对于-1.324的向上舍入操作,结果会变为-1。

3、向下舍入

当进行向-∞方向舍入(向下舍入)时,结果会被舍入为离它最近的较小值,也就是朝负无穷大的方向靠近。这意味着小数部分会被舍入为0,而整数部分会被减少到下一个整数。这种舍入模式通常由标准库函数floor()使用。

举个例子,如果我们对1.324进行向下舍入,结果会变为1。同样地,对于-1.324的向下舍入操作,结果会变为-2。

4、向0舍入

当进行向0舍入(直接截断)时,结果会朝接近0的方向舍入,也就是将多余的位数直接丢弃。这意味着小数部分会被截断为0,而整数部分保持不变。这种舍入模式通常在类型转换等操作中使用。

举个例子,如果我们对1.324进行向0舍入,结果会变为1。同样地,对于-1.324的向0舍入操作,结果会变为-1。

这些舍入模式的选择取决于具体的需求和应用场景,确保了在浮点数计算中的一致性和可预测性。

9、例子

#include <stdio.h>

int main()
{
	int n = 9;
	// 00000000 00000000 00000000 00001001

	float* pFloat = (float*)&n;
	printf("n的值 %d\n", n);//9

	printf("*pFloat的值 %f\n", *pFloat);//0.000000
	// 0 00000000 00000000000000000001001
	// +0.00000000000000000001001*2^-126
	// +1.001*2^-146

	*pFloat = 9.0;
	// 1001
	// 1.001*2^3
	// 0 10000010 00100000000000000000000

	printf("n的值 %d\n", n);//1091567616
	// 01000001 00010000 00000000 00000000
	// 1091567616

	printf("*pFloat的值 %f\n",*pFloat);//9.000000
	return 0;
}

运行结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值