文章目录

  • 起因
  • 浮点数的精度
    • IEEE754表示
    • 测试
    • 浮点数运算
    • 浮点数精度
      • 我的理解
        • 自己的理解
  • 总结
  • 参考

起因

今天遇到一个问题,角色卡在一个模型边上,在PVD看模型也比较正常。最终原因呢是因为模型的一个三角形的两个顶点非常进,结果在浮点数运算的时候这种非常小的差异就被丢掉了,所以在PhysX中会判定移动了距离为0的位置,所以一直就卡在原地,跳也跳不起来。来看下两个顶点的信息:

 [0] = {x = -4.10000086, y = -0.200000167, z = -3.56512594}[1] = {x = -4.10000086, y = -0.200000077, z = -3.56512594}

只有y值有一丁点的差异,这个时候起码还在浮点数的有效范围内。带上几个问题对研究这个问题会有帮助:

  • 1.为什么 -0.200000167和 -0.200000077浮点数表示不一样,最后加上4.76143503(运算用到的一个坐标)结果就一样了?是什么原因?
  • 2.浮点数运算的逻辑是什么?
  • 3.浮点数的精度为什么是7位?

浮点数的精度

IEEE754表示


测试

把上面出问题的数据抽出来测试以下

 float y = 4.76143503;float y1 = -0.200000167;float y2 = -0.200000077;float y1Add = y + y1;float y2Add = y + y2;std::cout << std::bitset<32>(*(_ULonglong*)&y) << std::endl;std::cout << std::bitset<32>(*(_ULonglong*)&y1) << std::endl;std::cout << std::bitset<32>(*(_ULonglong*)&y1Add) << std::endl;std::cout << std::bitset<32>(*(_ULonglong*)&y2) << std::endl;std::cout << std::bitset<32>(*(_ULonglong*)&y2Add) << std::endl;cout << "fTest = " << y1Add << endl;cout << "fTest1 = " << y2Add << endl;

结果可以看到:

 0100000010011000010111011010110110111110010011001100110011011000010000001001000111110111010001101011111001001100110011001101001001000000100100011111011101000110fTest = 4.56143fTest1 = 4.56143

小结

  • y1和y2的浮点数表示的确不一样,即IEEE754的23位小数位是满足条件的
  • 做了加法之后,结果变成一样了

那么接下来根据问题来分析原因是什么样的。

浮点数运算

参考[2],[3]的主要步骤:

  • 1.规格化表示
  • 2.对阶
  • 3.尾数标数
  • 4.规格化
  • 5.舍入
 IEEE 754 standard floating point Addition AlgorithmFloating-point addition is more complex than multiplication, brief overview of floating point addition algorithm have been explained belowX3 = X1 + X2X3 = (M1 x 2E1) +/- (M2 x 2E2)1) X1 and X2 can only be added if the exponents are the same i.e E1=E2.2) We assume that X1 has the larger absolute value of the 2 numbers. Absolute value of of X1 should be greater than absolute value of X2, else swap the values such that Abs(X1) is greater than Abs(X2).
Abs(X1) > Abs(X2).3) Initial value of the exponent should be the larger of the 2 numbers, since we know exponent of X1 will be bigger , hence Initial exponent result E3 = E1.4) Calculate the exponent's difference i.e. Exp_diff = (E1-E2).5) Left shift the decimal point of mantissa (M2) by the exponent difference. Now the exponents of both X1 and X2 are same.6) Compute the sum/difference of the mantissas depending on the sign bit S1 and S2.
If signs of X1 and X2 are equal (S1 == S2) then add the mantissas
If signs of X1 and X2 are not equal (S1 != S2) then subtract the mantissas7) Normalize the resultant mantissa (M3) if needed. (1.m3 format) and the initial exponent result E3=E1 needs to be adjusted according to the normalization of mantissa.8) If any of the operands is infinity or if (E3>Emax) , overflow has occurred ,the output should be set to infinity. If(E3 < Emin) then it's a underflow and the output should be set to zero.9) Nan's are not supported.

然后,我就自己手动计算了一下:

 01000000100110000101110110101101 = 4.76143503阶码:10000001 = 129 - 127 = 2尾数:1.00110000101110110101101--------------------------------------------------10111110010011001100110011011000 = -0.200000167阶码:01111100 = 124 - 127 = -3尾数:1.1001100110011001101100001000000100100011111011101000110 = 4.56143475 = 4.76143503 + -0.200000167阶码:10000001 = 129 - 127 = 2尾数:1.00100011111011101000110加法运算:1.1001 1001 1001 1001 1011 000对齐:小数点左移2-(-3) = 5位0.00001.1001 1001 1001 1001 1011 000相减:1.0011 0000 1011 1011 0101 101-    1.0000 1100 1100 1100 1100 110  11 000=    1.0010 0011 1110 1110 1000 111比较    1.0010 0011 1110 1110 1000 110--------------------------------------------------10111110010011001100110011010010 = -0.200000077阶码:01111100 = 124 - 127 = -3尾数:1.1001100110011001101001001000000100100011111011101000110 = 4.56143475 = 4.76143503 + -0.200000077阶码:10000001 = 129 - 127 = 2尾数:1.00100011111011101000110加法运算:1.10011001100110011010010对齐:小数点左移2-(-3) = 5位0.0000110011001100110011010010相减:计算器去掉小数点和前面的1来的比较快1.0011 0000 1011 1011 0101 101-    0.0000 1100 1100 1100 1100 110  10010= 1.0010 0011 1110 1110 1000 111比较    1.0010 0011 1110 1110 1000 110

小结:

  • 计算的结果的确是一样的,但是和最终的4.76143503还是有点不一样(最后一个比特位),这说明我手动计算和机器计算还是有点区别,包括后面的规格化,舍入我就没去细看了,留个问题在这里吧
 // 4.76143503 + -0.2000001671.0010 0011 1110 1110 1000 111// 4.76143503 + -0.2000000771.0010 0011 1110 1110 1000 111// 最终结果的二进制1.0010 0011 1110 1110 1000 110
  • 这里解释了前两个问题:最主要是在浮点数的运算过程中(这里是加法),因为要对齐阶码,所以更小的数字的尾数后面几位(这里是5位)都忽略掉了,所以-0.200000167和 -0.200000077的差异也就没有了

再来看看IEEE754有23位小数,为什么精度是7位小数位?

浮点数精度

先确定下自己的问题:

  • 精度指的是十进制的小数点后面的个数,而IEEE754标准的23位指的是二进制有效位
  • 我想理解的是23位二进制有效位是如何换算到十进制的7位有效位的
  • 网上搜索了之后,最终可以确定的是十进制小数点后面可以精确到6-7位
  • 自己最终问题是如何用数学方法证明?

最常见的解释如下,参考[5][6]:

因为float类型的数值由二进制下的后23位决定的,而这后23位表示的十进制的数最大为2^23=8388608,也就是说在二进制下能表示的准确的23位的数转换 到十进制下最大的数是7位的,数值是多少不重要,因为这个数是在十进制下是7位,所以float在十进制下的精度位7位。再说白一点,二进制下能表示的最大的准确的数值转换为十进制是7位。

简单来讲,就是二进制可以表示的8388608是一个7位数字,但是并不能包括完全包括所有7位有效位,所以是6-7位有效位。但是,这里我还是有疑问的,这个算法应该是小数点前面可以这么算,小数点后面怎么算的,如果可以这么算,又如何解释?

我的理解

参考[7][8][12]之后,相对而言,我比较喜欢[8][12]的解释。但最终我自己理解又是另外一个。

  • 1.小数点后面的二进制和十进制转换应该是这样的表达式,当然应该要乘以一个0或者1表示有效位上面有数据

0.xxxx=2−1+2−2+...+2−230.xxxx = 2^{-1} + 2^{-2} + ... + 2^{-23}0.xxxx=2−1+2−2+...+2−23
那么,小数点后面最小的单位是$2^{-23} = $
这里其实也说明了浮点数的有些数值只能是近似表示的

  • 2.[8]数学推导

−log102−23=6.924-{log_{10}}{2^{-23}} = 6.924−log10​2−23=6.924

自己的理解

  • (1)23位小数,能表示的最小小数位,其他的小数都是乘以这个最小单位构成的[12]

2−23=0.000000119209289550781252^{-23}= 0.000000119209289550781252−23=0.00000011920928955078125

一开始我想,这后面明显有这么多小数,为什么是7位?

  • (2)这个最小单位表示的就是精度的尾数,怎么理解!
    如果最小单位是0.1,那么精度就是小数点后面1位,因为你永远也组合不了0.1后面的小数(整数个最小单位求和),比如0.01、0.02等
    如果最小单位是0.000001,那么精度就是小数点后面6位,同样的你也永远组合不了0.000001更小单位的小数,比如0.0000001、0.0000002等等
  • (3)准确来说二进制23位有效位表示的精度为小数点后面6~7位。
    由上面一条可以知道,0.00000011920928955078125这个最小单位用23位二进制任意组合(求最小单位的倍数),只能得到比这个0.00000011920928955078125更大的数。所以永远得不到0.00000001这样的小数(精度为小数点后面8位),但是肯定可以得到0.000001(精度为小数点后面6位)的小数。但是不能完全表示精度位小数点后面为7位的小数。下面忽略舍入的算法,只取小数点后面7位有效位的计算结果:
0.00000011920928955078125 * 1 = 0.0000001
0.00000011920928955078125 * 2 = 0.0000002
0.00000011920928955078125 * 3 = 0.0000003
0.00000011920928955078125 * 4 = 0.0000004
0.00000011920928955078125 * 5 = 0.0000005
0.00000011920928955078125 * 6 = 0.0000007
0.00000011920928955078125 * 7 = 0.0000008
0.00000011920928955078125 * 8 = 0.0000009
0.00000011920928955078125 * 9 = 0.000001

可以看到0.0000006表示不出来,如果考虑舍入的话可能是其他的表示不出来,所以说不能完全表示所有的小数点后面7位小数

总结

  • 浮点数的爱与恨

参考

[1]深入理解浮点数有效位

[2]二进制浮点数的加减法运算

[3]Floating Point Tutorial

[4]程序员必知之浮点数运算原理详解

[5]float的精度为什么是7位详解

[6]java浮点类型float和double的主要区别,它们的小数精度范围大小是多少?

[7]浮点计算精度损失原因

[8]单精度浮点数的有效数字为什么是7位,我算的明明是6位,你看我算的对吗?

[9]Why IEEE754 single-precision float has only 7 digit precision?

[10]In-depth: IEEE 754 Multiplication And Addition

[11]浮点数精度问题透析:小数计算不准确+浮点数精度丢失根源

[12]关于float型是单精度的有效位数是7位,为什么在下面的例子中这8位都是准确的呢?

[13]二进制计算器网页版

为什么单精度浮点数的精度是7位相关推荐

  1. 双精度改单精度c语言程序,C语言菜鸟基础教程之单精度浮点数与双精度浮点数...

    上节课 简单介绍了浮点数.计算机程序中的浮点数分为单精度浮点数和双精度浮点数. 单精度和双精度精确的范围不一样. 计算机里的最基本的存储单位用位(bit)来表示.bit只能用来存储0或1. 稍大一点的 ...

  2. C语言中的float(单精度浮点数)

    本文主要记录一下Float的一些基础知识. 在计算机界,有个规定叫IEEE754,它规定了如何以二进制的方式来存储10进制的数. 按照这个规定,单精度浮点数(float)这个数据类型所占内存大小为4个 ...

  3. c语言浮点型菜鸟教程,C语言菜鸟基础教程之单精度浮点数与双精度浮点数

    上节课 简单介绍了浮点数.计算机程序中的浮点数分为单精度浮点数和双精度浮点数. 单精度和双精度精确的范围不一样. 计算机里的最基本的存储单位用位(bit)来表示.bit只能用来存储0或1. 稍大一点的 ...

  4. c语言浮点数菜鸟教程,C语言菜鸟基础教程之单精度浮点数与双精度浮点数

    上节课 简单介绍了浮点数.计算机程序中的浮点数分为单精度浮点数和双精度浮点数. 单精度和双精度精确的范围不一样. 计算机里的最基本的存储单位用位(bit)来表示.bit只能用来存储0或1. 稍大一点的 ...

  5. 单精度浮点数 float、双精度浮点数 double

    3.3 浮点数表示法 内容导视: 小数对应的二进制 科学计数法 浮点数表示法 最大值.最小值 特殊值 3.3.1 小数对应的二进制 之前漏掉了小数对应的二进制,现补上. 二进制转十进制 从个位数开始向 ...

  6. c语言编程输入单精度浮点数,小朋友学C语言(4):单精度浮点数与双精度浮点数...

    上节课简单介绍了浮点数.计算机程序中的浮点数分为单精度浮点数和双精度浮点数. 单精度和双精度精确的范围不一样. 计算机里的最基本的存储单位用位(bit)来表示.bit只能用来存储0或1. 稍大一点的单 ...

  7. 单精度浮点数(Float)与双精度浮点数(Double)

    前言 对于十进制的整数使用二进制表示很简单,但是对于十进制小数如何使用二进制进行存储?十进制的小数又何如使用二进制小数表示?此文章描述了如何将十进制小数转换为二进制小数以及浮点数再内存中时如何进行存储 ...

  8. 单精度浮点数(float)加法计算出错

    场景: 一个float型的变量赋值1170601,加上19000000,结果出现错误. 原因: float占用4个字节(32位)存储空间,包括符号位1位,阶码位8位,尾数23位.浮点数精度与它的尾数有 ...

  9. IEEE 754 单精度浮点数转换

    浮点型数据通讯的时候,需要将浮点数转成对应的四字节内存数据,可以通过该网址工具验证. IEEE 754 单精度浮点数转换 http://www.styb.cn/cms/ieee_754.php 1.I ...

最新文章

  1. xx.xib: error: Illegal Configuration: Safe Area Layout Guide before iOS 9.0报错问题解决
  2. ORA-14452的出现原因解析及解决方法
  3. PHP解析JSON数据的源代码
  4. 算法提高 日期计算c语言,算法提高 日期计算
  5. 个性潮流的设计PSD分层模板
  6. HTML5+学习笔记2-------边看代码边研究貌似还是有点问题...还在研究中api中
  7. 【GitHub】提交新项目、更新已有的项目
  8. FFMPEG 常用命令行
  9. servicemix7 linux,学习ServiceMix笔记(三) 学习ServiceMix的基本命令之安装组件
  10. C++ Primer 第5版--练习10.35
  11. potoshope cs5 序列号
  12. java中的math_Java中math类的常用方法
  13. 【硬件】【RF 连接器】
  14. 在大学城开一间宾馆能挣多少钱?
  15. 操作系统:手把手带你扫盲 操作系统 的那些必知必会!
  16. 软件测试-柠檬班python全栈自动化50期测试学习笔记分享
  17. L2-4 哲哲打游戏 (25 分)_模拟
  18. 【无人机】基于A星算法实现三维栅格地图路径规划matlab代码
  19. Android 控制LED 屏
  20. tracer静态和缺省路由配置_Ciso Packet Tracer配置静态路由及默认路由(一)

热门文章

  1. 编程一直犯低级错误怎么办_大多数学生在学习编程时犯的错误
  2. 中国中心城市和都市圈发展指数发布:成都天津位次紧跟北上深广,合肥提升最明显,大连下跌幅度较大...
  3. 【QT学习】Graphics View框架(基础篇)- 图元、场景、视图
  4. 面经|小米-未来星-数据科学家|一面|70min
  5. 静态建模(类图和对象图)
  6. 互联网运营到底是做什么的?要做的事情,其实就是这四个模块
  7. python从txt中批量读取图片名后将图片另存为
  8. 男人最吸引女人的40个瞬间
  9. 本特利传感器330901-00-24-05-02-05
  10. 对一款远控的个人分析