《Unity3D高级编程之进阶主程》第一章 C#要点技术(三) 浮点数的精度问题

本文主要是介绍《Unity3D高级编程之进阶主程》第一章 C#要点技术(三) 浮点数的精度问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        浮点数计算在我们大多数项目中并没有使用到特别的科学计算部分,所以float基本都够用。double也同样有精度问题,无论怎么样都是无法避免精度导致的在逻辑中的不一致的问题。

根据 IEEE 754 标准,任意一个二进制浮点数 F 均可表示为:

F = (-1 ^ s) * (1.M) * (2 ^ e)  

  • s:符号位, 0/1
  • M:尾数部分,具体的小数用二进制表示
  • e:比例因子的指数,浮点数的指数

float


      名称

长度        比特位置
符号位Sign(S)1bit(b31)
指数部分Exponent(E)8bit(b30-b23)
尾数部分Mantissa(M)23bit(b22-b0)

 double

名称

长度        比特位置
符号位Sign  (S)1bit  (b63)
指数部分Exponent(E)11bit   (b62-b52)
尾数部分Mantissa(M)52bit    (b51-b0)

        其中E的阶符采用隐含方式,即采用移码(补码)方法来表示正负指数。例如float的8位价码,应将指数e加上一个固定的偏移值127(01111111),即 e 加上 127才是存储在二进制中的数据。

        尾数M只表示1后面的小数部分,而且是二进制直译的那种,然后再根据阶码来平移小数点。

        例:9.625 => 1001.101 => 1.001101×(2^3) => 00110100000000000000000 去掉了小数点左侧的 1,并用 0 在右侧补齐。

        整数部分采用 “除2取余法”,小数部分则采用 “乘2取整法”。

浮点数的精度问题

例:198903.19 
        110000100011110111.0011000010100011(截取 16 位小数)
        198903.19(10) = (-1 ^ 0) * 1.100001000111101110011000010100011 * (2 ^ 17)

      小数部分 0.19 转为二进制后,小数位数超过 16 位(已经手算到小数点后 32 位都还没算完,其实这个位数是无穷尽的),因此这里导致浮点数有诸多精度的问题,它很多时候无法准确的表示数字,甚至非常不准确。

精度问题

         浮点数的精度问题不只是小数点的精度问题,随着数值越来越大,即使是整数也开始会有相同的问题,因为浮点数本身是一个 1.M * (2 ^ e) 公式形式得到的数字,当数字放大时,M的尾数的存储位数没有变化,能表达的位数有限,自然越来越难以准确表达,特别是数字的末尾部分越来越难以准确表达。

常见问题

  1. 数值比较不相等
  2. 数值计算不确定,比如1f / (1f /5555f * 11110f) 结果有可能是0.4999999999991
  3. 不同设备计算结果不同,不同平台上的浮点数计算也有所偏差

解决方案

  1. 只计算一次,认定这个值为准确值,只用这个变量结果做判断,也省去了多次计算浪费的CPU
  2. 改用int或long型来替代浮点数
  3. 用定点数保持一致性并缩小精度问题。定点数把整数部分和小数部分拆分开来,都用整数的形式表示。缺点是由于拆分了整数和小数,两个部分都要占用空间,所以受到存储位数的限制。用定点数来做计算能保证在各设备上的计算结果一致性。
  4. 最耗的办法,用字符串替代浮点数。
  5. 最差的办法,提高期望值。

C# decimal

        它的内部实现就是定点数的实现方式,我们完全可以把它看作定点数来操作。

        它占用128位的存储空间即一个decimal变量占用16个字节。

        它的数值范围在 ±1.0 × 10^28 到 ±7.9 × 10^28之间,这么大的的占用空间却比float的取值范围还小。

        decimal 精度比较大,精度范围在 28 个有效位,另外任何与它一起计算的数值都必须先转化为 decimal 类型否则就会编译报错,数值不会隐式的自动转换成 decimal。

        精度过大导致CPU 计算消耗量大,大量使用时会使得堆栈内存直线飙升,这也间接的增大了CPU的消耗。

        大部分项目自己实现定点数。

这篇关于《Unity3D高级编程之进阶主程》第一章 C#要点技术(三) 浮点数的精度问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1082247

相关文章

C#中Guid类使用小结

《C#中Guid类使用小结》本文主要介绍了C#中Guid类用于生成和操作128位的唯一标识符,用于数据库主键及分布式系统,支持通过NewGuid、Parse等方法生成,感兴趣的可以了解一下... 目录前言一、什么是 Guid二、生成 Guid1. 使用 Guid.NewGuid() 方法2. 从字符串创建

Python中你不知道的gzip高级用法分享

《Python中你不知道的gzip高级用法分享》在当今大数据时代,数据存储和传输成本已成为每个开发者必须考虑的问题,Python内置的gzip模块提供了一种简单高效的解决方案,下面小编就来和大家详细讲... 目录前言:为什么数据压缩如此重要1. gzip 模块基础介绍2. 基本压缩与解压缩操作2.1 压缩文

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出