• 浮点数 C语言 IEEE754


    知识内化:用自己的语言讲述一遍,把复杂的东西解释得简单透彻

    计算机表示浮点数的问题:(自己分析一下这个问题)

    输入是:任意一个浮点数,正无穷到负无穷,包括整数部分和小数部分 22222.99999999

    限制是:计算机只有32bit来表示一个浮点数

    输出是:如何利用这32bit来表示浮点数,尽可能范围大,精度高

    方案探索

    科学计数法一样,10^20 =1e10不用自己用20个十进制的位来写,而是写成1.0*10^20

    也就是科学计数法可以只写小数部分指数部分,来表示一个范围更大的数,原来的计数方法是通过乘法和加法来表示一个数,现在是通过指数来表示,因此更少的bit可以表示更大的数

    原来的方案:4bit   x = [1,2,3,4],  y = [power(2,i) for i in x]=[2,4,8,16],最多表示到16= 2^4

    科学计数法的方案: 4bit r= [2,4,8,16] , y = [power(2,i) for i in r]=[4,16, 256, 65536], 最多可以表示 2^16

    用画图就可以很快看到两者的区别

     方案细化

    接下来的问题是,我们讨论,多少bit给指数,多少bit给小数,精度和范围是多少

    本质通用的二进制小数的表示位:类比十进制

     因为编码长度有限,就像十进制不能精确表示1/3,只能表示有限位的十为基底的数,那么二进制小数也只能精确表示以2为底的数,1/5 = 0.2 只能近似表示为 0.125+ 0.0xxxx + xxx+xxx

    小数部分(有效数字):23 位给小数,精度可以是 2^{23} \rightarrow (2^{10})^{2.3} \rightarrow ~ (10^3)^{2.3} \rightarrow 10^{6.9}

    2^{-23}\rightarrow 10^{-6.9} 大概是1e-6, 1e-7的精度

    指数部分(阶码):8位, 带符号的 2^{-128} \rightarrow 2^{127}     ,大概范围是 2^{128}\rightarrow (2^{10})^{12.8}\rightarrow (10^{3})^{12.8}\rightarrow 10^{ 38.4}  大概是 10的正负38次方

    符号位:1位

    一般表示:1.001101 *2^n 

    问题:

    • 0怎么表示
    • 正负无穷怎么表示
    • 一般数字怎么表示
    • NaN 怎么表示(无效数字,不合法,比如除以0)

    针对这个问题提出的解决方案,根据exp分成3中情况,全0,全1,非0非1

    再分别看三种情况

    1.第一种情况,本来8bit可以表示0~255,扣除全零全一,剩下1~254,我们默认偏移127, 得到-126~+127。 小数部分 M=1+f, 隐藏1开头的

     2. 第二种情况,指数为0,小数部分M=f  可以表示0,要意识到一个问题就是,ieee754的浮点数有两个0的表示,+0,-0

    3.第三种情况:特殊值:正负无穷,NaN

    下面的图是float8 e4m3

    但有时候要跳出来,比如ieee754为什么NaN不定义为全为1,这样还可以释放exp=1111的情况、我在fp8的定义中就看到这个这个释放

    float8 in DL

    最近一个同事问我,为什么浮点计算遇到reduction不能自动向量化,提升unsfe,会改变计算顺序,我写出了下面这个demo. 本质是unroll 4就改变了计算顺序

    1. #include <stdio.h>
    2. int main()
    3. {
    4. float f1 = -1e10f;
    5. float f2 = 1e-3f;
    6. float s = 1e10f;
    7. s = s + f1;
    8. s = s + f2;
    9. float p = 1e10f;
    10. float temp = (f1 + f2);
    11. p = p + temp;
    12. printf("s=%f, p = %f\n", s, p); // 0.001 p = 0
    13. return 0;
    14. }

     最后再 v[0]+=v[1]  

                v[0]+=v[2]

               v[0]+=v[3]

    s就是v[0]

    本质原因是浮点数只有6到7位的有效数字,1+10^8,有效数字就不够表示了。

    mitchell近似乘法计算

    expbias= 127 = 0 0111 1111 +0*23

                   = 0011 1111 1000 0000 0000 0000 0000

                  = 0x3f80000

    1. float a = 12.3f;
    2. float b = 4.56f;
    3. int c = *(int*)&a + *(int*)&b - 0x3f800000;
    4. printf("近似结果:%f\n", *(float*)&c);
    5. printf("精确结果:%f\n", a * b);
    6. return 0;

    如何操作浮点数的bit, 这里有一个链接:https://github.com/myisabella/datalab/blob/master/bits.c#L350

    example

    ​​​​​​​

     

    有效数字

    1. #include <stdio.h>
    2. int main()
    3. {
    4. float a = 1e8;
    5. float b = a + 1.0;
    6. float c = b - a;
    7. printf("%f %f %f\n", a, b, c); // 1e8 结果为01e7,结果为1,有效数字
    8. }

    最后我写了一个python脚本,把float的编码翻译过程写了一下,对应上面的一个表和float32的一个例子

    1. class binary:
    2. def __init__(self, b_str):
    3. self.str = b_str
    4. self.v = int(b_str, 2)
    5. @classmethod
    6. def from_int(cls, int_v):
    7. return cls(bin(int_v)[2:])
    8. class Float:
    9. def __init__(self, num_exp, num_frac, exp_str, frac_str):
    10. self.num_exp = num_exp
    11. self.num_frac = num_frac
    12. self.exp_bias = int('1'*(self.num_exp-1), 2)
    13. self.frac_norm = 2**self.num_frac
    14. self.exp = binary(exp_str)
    15. self.frac = binary(frac_str)
    16. f = self.frac.v/self.frac_norm
    17. if self.exp.v == 0:
    18. # denormalized:all exp zero
    19. self.E = 1 - self.exp_bias
    20. self.M = f/self.frac_norm
    21. elif self.exp.v == int('1'*self.num_exp, 2):
    22. if self.frac.v == 0:
    23. print("inf")
    24. else:
    25. print("NaN")
    26. else:
    27. # normalized
    28. self.E = self.exp.v - self.exp_bias
    29. self.M = 1 + f
    30. self.v = 2**(self.E)*self.M
    31. def __str__(self):
    32. return f"{self.exp.str}:{self.frac.str}, E={self.E}, M={self.M}, v={self.v}"
    33. class FloatE4M3(Float):
    34. def __init__(self, exp_str, frac_str):
    35. super().__init__(4, 3, exp_str, frac_str)
    36. class FloatE8M23(Float):
    37. def __init__(self, exp_str, frac_str):
    38. super().__init__(8, 23, exp_str, frac_str)
    39. data = [FloatE4M3('0000', '000'),
    40. FloatE4M3('0000', '001'),
    41. FloatE4M3('0000', '111'),
    42. FloatE4M3('0001', '000'),
    43. FloatE4M3('1110', '111')]
    44. for i in data:
    45. print(i)
    46. assert FloatE8M23('10000101', '10100110100000000000000').v == 105.625

  • 相关阅读:
    Day29 单元测试 pytest
    NFTScan 正式上线 TON NFTScan 浏览器!
    JS实现二叉排搜索树
    Java 线程安全的集合有哪些?
    Leetcode 450. 删除二叉搜索树中的节点
    Spring 源码(7)Spring的注解是如何解析的?
    令人困惑的 Go time.AddDate
    App自动化测试框架设计与实现
    Delphi 开发so库,Delphi 调用SO库
    【超详细】7000字+24张图带你彻底弄懂线程池
  • 原文地址:https://blog.csdn.net/Chunying27/article/details/127813389