【C语言】浮点数在内存中的存储(详解)

引言

markdown语法小知识点

写在前面,markdown语法的小知识点

如何实现文字变红且加上了底色?如hello world

markdown语法如下即可!

`hello world`

以及页内跳转

<span id="jump">这一句话没啥用</span>
[回到开头](#jump)

正题

之前学习完了整形、字符类型在内存中的存储,今天让我们来看看float类型!

整数类型👉【int】

字符类型👉【char】

常见的浮点数

3.14159
1E10

浮点数家族包括floatdoublelong double 类型。

而浮点数表示的范围是在头文件<float.h>里面定义的。

需要了解的是

如果你打出3.14,编译器默认是double类型的。若想让他为float类型,则要在前面加f

1E10是科学计数法,代表1.0×10^10

代码引例

这一句话没啥用

先来看看下面这串代码

int main()
{
	int n = 9;
	float* pfloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pfloat的值为:%f\n", *pfloat);
	*pfloat = 9.0;
	printf("num的值为:%d\n", n);
	printf("*pfloat的值为:%f\n", *pfloat);
	return 0;
}

运行的结果如下

image-20220110135611980

指针pfloat保留的是强制转换为float类型的int变量n

准确来说,是将int指针类型强制转换为了float指针类型

那打印的结果不应该是9吗?为什么是0.000000呢?

再来看看后面的代码,我们让*pfloat=9.0,用%d打印的时候,却打印出了一串不知道怎么来的很大的数字。这又是为什么呢?

一个涉及到的小知识点

  • 不管是double类型,还是float类型,默认小数点后都有6位
  • 我们可以用%.f的方式来控制打印,如%.3f就是只打印到小数点后3位

答案只有一个:浮点型在内存中的存储方式和int类型完全不同!

浮点型如何在内存中存放?

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
( − 1 ) S ∗ M ∗ 2 E (-1)^S * M * 2^E (1)SM2E

  • (-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。

  • M表示有效数字,大于等于1,小于2。

  • 2^E表示指数位。

image-20220110141551509

十进制&二进制的科学计数法

我们在小学就学到过,1.234×10^2=123.4

而二进制中,其实就是把底数的10变成了2,1.011*2^2=101.1

记住以下这个结论即可

二进制码M乘以2的n次方,相当于将二进制码M的小数点向右移动n位

S\M\E如何判断?

我们以5.5为例,它的二进制是101.1,相当于

( − 1 ) 0 ∗ 1.011 ∗ 2 2 (-1)^0 * 1.011 * 2^2 (1)01.01122

和上面的公式比对,我们可以读出来 S=0,M=1.011,E=2

在之前的学习中,我们知道float类型占用4个字节的空间,而double类型则是8个字节

浮点类型的内存空间示意图

image-20220425204828194

float类型的S\E\M被分区存放在这4个字节的内存空间中

同理,double类型的S\E\M也是分区存放,它的有效数字长于float类型

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

image-20220425204844636

IEEE754对M的特殊规定

因为是二进制数,1≤M<2,而M可以写成 1.xxxxxx 的形式,其中xxxxxx是小数部分

IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分

比如保存1.01的时候,只保存小数点后的01,等到读取的时候,再把第一位的1加上去

这样做的目的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

我在学习的时候,关于这个24位有效数字曾产生了疑惑。

实际上它并不难理解:

在内存中,32位浮点数的M有23位的空间,如果我们保存了小数点前面的1,就只能保存小数点后22位的内容。

但如果我们省略1,只保留小数点后的内容,那不就能保存到小数点后第23位了吗?再加上原来小数点前的1,不就是24位有效数字了!

IEEE754对指数E的特殊规定

E是一个无符号整数(unsigned int)

  • 如果E为8位,它的取值范围是0-255
  • 如果E为11位,它的取值范围是0-2047

可是科学计数法里面的E是可以出现负数的。

所以IEEE754规定,存入内存时E的真实值必须再加上一个中间数

  • 8位的E,中间数是127
  • 11位的E,中间数是1023

例:2^10的E是10,所以保存为32位浮点数的时候,E必须保存为10+127=137,即10001001

保存为64位浮点数的时候,E保存为10+1023=1033,即10000001001

①当E不为全0或全1时

浮点数采用下面的规则来进行存放:

内存中指数E的计算值减去127(或1023),得到E的真实值,再将有效数字M前面加上第一位的1

以32位浮点数举例

0.5的二进制形式为0.1。科学计数法中整数部分必须为1,小数点应右移一位

则为1.0*2^(-1),E的真实值为-1,存放在内存中为-1+127=126(01111110)

M存放小数点后的0,补全23位,全为0

这时候0.5的二进制表现形式就是

0 01111110 00000000000000000000000

对应S、E、M的部分如下表所示

SEM
00111111000000000000000000000000

②当E为全0时

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值

有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数

这样做是为了表示±0,以及接近于0的很小的数字

③当E为全1时

当E为全1时,原E为128,数字非常大,相当于无穷大

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

解释开篇代码

回到开头

整形9的原码如下

00000000 00000000 00000000 00001001

当我们将它强制存放到float类型的指针中时

SEM
0000000000000000 00000000 00001001

解码出来就是
( − 1 ) 0 ∗ 0.00000000000000000001001 ∗ 2 − 126 (-1)^0*0.0000000 00000000 00001001*2^{-126} (1)00.000000000000000000010012126
这是一个很小的数字,远小于float类型默认的小数点后六位,所以printf打印的是0.000000

int n;
float* pfloat = &n;
*pfloat = 9.0; // 以浮点数形式存入了整形n的地址空间
printf("num的值为:%d\n", n);
printf("*pfloat的值为:%f\n", *pfloat);

这里9.0就是以浮点数的形式存入float指针的

  • 9.0 十进制
  • 1001.0 二进制
S=0, M=1.001, E=3
二进制码
0100 0001 0001 0000 0000 0000 0000 0000

开启调试,在内存框中查看n的地址如下

image-20220110160103813

41100000
0100 00010001 00000000 00000000 0000

正好对应了浮点数9.0在内存中存放的二进制码

最后n以%d整形的方式打印出来,就是我们看到的1091567616

结语

考试周快要结束啦!寒假将开始新的代码学习

终于补上了之前欠下的博客了,当作是一种复习吧,的确有不少东西已经忘记的差不多了😥

感谢你看到最后,点个赞再走吧!

  • 61
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
目录 1. C 语言的指针和内存泄漏 5 2. C语言难点分析整理 10 3. C语言难点 18 4. C/C++实现冒泡排序算法 32 5. C++指针和引用的区别 35 6. const char*, char const*, char*const的区别 36 7. C可变参数函数实现 38 8. C程序内存组成部分 41 9. C编程拾粹 42 10. C语言实现数组的动态增长 44 11. C语言的位运算 46 12. 浮点数存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. C语言复杂表达式的执行步骤 66 16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与return的差别 141 30. extern与static存储空间矛盾 145 31. PC-Lint与C\C++代码质量 147 32. spirntf函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 170 35. 内存对齐与ANSI Cstruct内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 186 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针 224 46. 标准C字符串分割的方法 228 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解C语言指针的奥秘 236 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 265 54. 结构体对齐的具体含义 269 55. 连连看AI算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级C语言程序员测试必过的十六道最佳题目+答案详解 297 61. C语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 380 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C和C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 389 76. 顺序表及其操作 390 77. 单链表的实现及其操作 391 78. 双向链表 395 79. 程序员数据结构笔记 399 80. Hashtable和HashMap的区别 408 81. hash 表学习笔记 410 82. C程序设计常用算法源代码 412 83. C语言有头结点链表的经典实现 419 84. C语言惠通面试题 428 85. C语言常用宏定义 450
目录 1. C 语言的指针和内存泄漏 5 2. C语言难点分析整理 10 3. C语言难点 18 4. C/C++实现冒泡排序算法 32 5. C++指针和引用的区别 35 6. const char*, char const*, char*const的区别 36 7. C可变参数函数实现 38 8. C程序内存组成部分 41 9. C编程拾粹 42 10. C语言实现数组的动态增长 44 11. C语言的位运算 46 12. 浮点数存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. C语言复杂表达式的执行步骤 66 16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与return的差别 141 30. extern与static存储空间矛盾 145 31. PC-Lint与C\C++代码质量 147 32. spirntf函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 170 35. 内存对齐与ANSI Cstruct内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 186 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针 224 46. 标准C字符串分割的方法 228 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解C语言指针的奥秘 236 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 265 54. 结构体对齐的具体含义 269 55. 连连看AI算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级C语言程序员测试必过的十六道最佳题目+答案详解 297 61. C语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 380 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C和C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 389 76. 顺序表及其操作 390 77. 单链表的实现及其操作 391 78. 双向链表 395 79. 程序员数据结构笔记 399 80. Hashtable和HashMap的区别 408 81. hash 表学习笔记 410 82. C程序设计常用算法源代码 412 83. C语言有头结点链表的经典实现 419 84. C语言惠通面试题 428 85. C语言常用宏定义 450
目录 1. C 语言的指针和内存泄漏 5 2. C语言难点分析整理 10 3. C语言难点 18 4. C/C++实现冒泡排序算法 32 5. C++指针和引用的区别 35 6. const char*, char const*, char*const的区别 36 7. C可变参数函数实现 38 8. C程序内存组成部分 41 9. C编程拾粹 42 10. C语言实现数组的动态增长 44 11. C语言的位运算 46 12. 浮点数存储格式: 50 13. 位域 58 14. C语言函数二维数组传递方法 64 15. C语言复杂表达式的执行步骤 66 16. C语言字符串函数大全 68 17. C语言宏定义技巧 89 18. C语言实现动态数组 100 19. C语言笔试-运算符和表达式 104 20. C语言编程准则之稳定篇 107 21. C语言编程常见问题分析 108 22. C语言编程易犯毛病集合 112 23. C语言缺陷与陷阱(笔记) 119 24. C语言防止缓冲区溢出方法 126 25. C语言高效编程秘籍 128 26. C运算符优先级口诀 133 27. do/while(0)的妙用 134 28. exit()和return()的区别 140 29. exit子程序终止函数与return的差别 141 30. extern与static存储空间矛盾 145 31. PC-Lint与C\C++代码质量 147 32. spirntf函数使用大全 158 33. 二叉树的数据结构 167 34. 位运算应用口诀和实例 170 35. 内存对齐与ANSI Cstruct内存布局 173 36. 冒泡和选择排序实现 180 37. 函数指针数组与返回数组指针的函数 186 38. 右左法则- 复杂指针解析 189 39. 回车和换行的区别 192 40. 堆和堆栈的区别 194 41. 堆和堆栈的区别 198 42. 如何写出专业的C头文件 202 43. 打造最快的Hash表 207 44. 指针与数组学习笔记 222 45. 数组不是指针 224 46. 标准C字符串分割的方法 228 47. 汉诺塔源码 231 48. 洗牌算法 234 49. 深入理解C语言指针的奥秘 236 50. 游戏外挂的编写原理 254 51. 程序实例分析-为什么会陷入死循环 258 52. 空指针究竟指向了内存的哪个地方 260 53. 算术表达式的计算 265 54. 结构体对齐的具体含义 269 55. 连连看AI算法 274 56. 连连看寻路算法的思路 283 57. 重新认识:指向函数的指针 288 58. 链表的源码 291 59. 高质量的子程序 295 60. 高级C语言程序员测试必过的十六道最佳题目+答案详解 297 61. C语言常见错误 320 62. 超强的指针学习笔记 325 63. 程序员之路──关于代码风格 343 64. 指针、结构体、联合体的安全规范 346 65. C指针讲解 352 66. 关于指向指针的指针 368 67. C/C++ 误区一:void main() 373 68. C/C++ 误区二:fflush(stdin) 376 69. C/C++ 误区三:强制转换 malloc() 的返回值 380 70. C/C++ 误区四:char c = getchar(); 381 71. C/C++ 误区五:检查 new 的返回值 383 72. C 是 C++ 的子集吗? 384 73. C和C++的区别是什么? 387 74. 无条件循环 388 75. 产生随机数的方法 389 76. 顺序表及其操作 390 77. 单链表的实现及其操作 391 78. 双向链表 395 79. 程序员数据结构笔记 399 80. Hashtable和HashMap的区别 408 81. hash 表学习笔记 410 82. C程序设计常用算法源代码 412 83. C语言有头结点链表的经典实现 419 84. C语言惠通面试题 428 85. C语言常用宏定义 450
C语言浮点数使用IEEE 754标准来进行存储。IEEE 754标准规定了浮点数的表示方法,包括浮点数的符号位、指数位和尾数位。 在C语言浮点数使用float和double两种数据类型进行表示。float类型占用4个字节,double类型占用8个字节。 浮点数存储方式可以分为三个部分: 1. 符号位(Sign):浮点数的符号位用一个bit来表示,0表示正数,1表示负数。 2. 指数位(Exponent):浮点数的指数位用一定的位数来表示。在float类型,指数位占用8个bits,而在double类型,指数位占用11个bits。指数位的值通过偏移码来表示,偏移码是指将真实的指数值加上一个偏置值,这个偏置值在float和double类型分别是127和1023。指数位的值表示了浮点数的数量级。 3. 尾数位(Significand or Mantissa):浮点数的尾数位用一定的位数来表示。在float类型,尾数位占用23个bits,而在double类型,尾数位占用52个bits。尾数位的值表示了浮点数的精度和小数部分。 浮点数的实际值可以通过以下公式计算得到:实际值 = (-1)^符号位 × (1 + 尾数位) × 2^(指数位 - 偏置值)。 浮点数存储方式使得它可以表示非常大和非常小的数,并且具有一定的精度。然而,由于浮点数存储方式以及精度问题,对于一些比较精确的计算,可能需要使用其他更为精确的数据类型或者进行一些特殊的处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕雪华年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值