【Unity3d】C#浮点数丢失精度问题

一、float、double浮点数丢失精度问题 

Unity3D研究院之被坑了的浮点数的精度(一百零三) | 雨松MOMO程序研究院

https://segmentfault.com/a/1190000041768195?sort=newest

浮点数丢失精度问题是由于大部分浮点数在IEEE754规范下就是无法准确以二进制形式存储的,如果无法准确存储,那么自然也无法准确计算数值。

float:4字节 32位 (1位符号位  8位指数位  23小数部分)
double:8字节 64位 (1位符号位 11为指数位 52为小数部分)

decimal的存储计算情况还没了解,而float的情况和double一样,其小数部分是23位存储,但实际有效范围是2^24,double是2^53,因为“IEEE规定,规约化之后的二进制小数,小数点左边必定是1,因此这23位可以表达的最大数值是1.11111111111111111111111(二进制),一共是24位。”

也就是说实际上存储的二进制形式小数,个位的1是约定俗成的(必然是1)所以就节省了这一位的存储了,只存储后面的小数部分(23位)【double类型同理】

下面C#案例解释0.1浮点数为什么无法准确存储【代码使用了Odin插件,可以自行去掉】

准确的IEEE754规范的十进制浮点数转化二进制浮点数公式:
整数部分:除2取余(倒序 高位写入二进制位)
小数部分:乘2取整(顺序 高位写入二进制位)
符号位:0代表正数、1代表负数
指数位:采用增量偏移存储(float32是增量2^7-1,即127) 也就是指数是-4的话会-4+127=123转二进制存储。(double则是增量2^10-1,即1023)


浮点数: 0.1f
符号位0(1)
指数位(8)123 - 127(-4)指数
小数位(23)   总共1 + 8 + 23 = 32位
0 0111 1011 10011001100110011001101
1.10011001100110011001101 * 2 ^ (-4)  注意: 23位是存储小数部分的,IEEE754规则是只存小数部分,实际计算时小数点左边必定是1,2 * (-4)就是小数点位左移4次
0.000110011001100110011001101
上面理解为: 0代表0个0.5  其他则是 0个0.25  0个0.125 1个0.0625  1个0.03125....之和

验算小数位是否准确如下,  0.1 取小数部分 * 2取整, 直至没有小数部分为止 取到的整数按顺序作为二进制位(高位到低位)
0.1 * 2 = 0.2   0

0.2 * 2 = 0.4   0
0.4 * 2 = 0.8   0
0.8 * 2 = 1.6   1
0.6 * 2 = 1.2   1

0.2 * 2 = 0.4   0....注意到进入了一个死循环(0.2、0.4、0.8、0.6 四个数乘2都会有小数,无法乘2得到小数是0的)
0.000110011001100110011001101 这是我们得到的浮点数二进制位显示
仔细发现最后是0011 0011 01,为什么末尾是01 而不是00?猜测是计算机会偏向较大值进位
正常应该是0011 0011 0011 无限循环下去的二进制,由于float32是只有23位因此末尾会被压缩为01

using System;
using Sirenix.OdinInspector;
using UnityEngine;public class MyBigDecimal : MonoBehaviour
{public float num;[HideLabel][LabelText("IEEE754")]public string ieee754;[Button("显示IEEE754二进制字符串形式", ButtonSizes.Large)]public void ShowIEEE754(){float floatValue = num;uint ieee754Value = FloatToIEEE754Converter.ConvertToIEEE754(floatValue);ieee754 = Convert.ToString(ieee754Value, 2).PadLeft(32, '0');//浮点数: 0.1f//符号位0(1)//指数位(8)123 - 127(-4)指数//小数位(23)   总共1 + 8 + 23 = 32位//0 0111 1011 10011001100110011001101//1.10011001100110011001101 * 2 ^ (-4)  注意: 23位是存储小数部分的,IEEE754规则是只存小数部分,实际计算时小数点左边必定是1,2 * (-4)就是小数点位左移4次//0.000110011001100110011001101//上面理解为: 0代表0个0.5  其他则是 0个0.25  0个0.125 1个0.0625  1个0.03125....之和//验算小数位是否准确如下,  0.1 取小数部分 * 2取整, 直至没有小数部分为止 取到的整数按顺序作为二进制位(高位到低位)//0.1 * 2 = 0.2   0//0.2 * 2 = 0.4   0//0.4 * 2 = 0.8   0//0.8 * 2 = 1.6   1//0.6 * 2 = 1.2   1//0.2 * 2 = 0.4   0....注意到进入了一个死循环(0.2、0.4、0.8、0.6 四个数乘2都会有小数,无法乘2得到小数是0的)//0.000110011001100110011001101 这是我们得到的浮点数二进制位显示//仔细发现最后是0011 0011 01,为什么末尾是01 而不是00?猜测是计算机会偏向较大值进位//正常应该是0011 0011 0011 无限循环下去的二进制,由于float32是只有23位因此末尾会被压缩为01}public class FloatToIEEE754Converter{public static uint ConvertToIEEE754(float floatValue){uint num = 0;byte[] bytes = BitConverter.GetBytes(floatValue);if (BitConverter.IsLittleEndian)Array.Reverse(bytes);foreach (byte b in bytes){num <<= 8;num |= b;}return num;}}
}

二、C#、Java解决丢失精度办法

C# 解决办法就是使用decimal,但运算会变慢,decimal为什么能解决精度问题?
decimal类型为什么比float和double精确?

C#中的decimal数据类型是一种用于表示高精度十进制数的数据类型,主要用于金融和其他需要精确计算的场景。

decimal有16字节,128位(bits)
0~15     保留位  16bits
16~23   小数点(左移位数) 8bits
24~30   保留位   7bits
31         符号位(0正数 1负数) 1bits
32~63   高位   32bits
64~95   中位   32bits
96~127 低位   32bits
假设想表达0.6的decimal二进制位形式如下:(与上面对应7行)

0000 0000 0000 0000
0000 0001
0000 000
0
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0110

计算会将32~127位合并计算出整数是6,符号位31位是0,小数点是1,因此计算得到
(-1^0 * 6) * 10^-1 = 1 * 6 * 0.1 = 0.6 (实际上程序并不会跑我这写的这段代码,因为所有运算+-*/都会用decimal的形式计算,而不是我们理解的运算方法了,如果想把decimal转成小数显示出来,那大概会只是在字符串形式上进行拿到整型后 对小数点字符进行左移而已,猜测是如此)

Java的BigDecimal说明:为什么float和double运算会丢失精度?BigDecimal就一定靠谱?_float转double型会丢精度吗-CSDN博客

C#没有BigDecimal类,github搜索到文章,使用BigInteger实现的。
https://github.com/dparker1/BigDecimal

github: BigDecimal开根号Sqrt方法的算法:竖式开根法——没有计算器也能手算开根

三、为什么十进制浮点数转二进制浮点数的公式是整数除2求余(倒序),小数乘2取整(顺序) 

例如:11.25(十进制浮点数)转二进制浮点数B
根据二进制位的存储规则小数点前的部分是2^(位阶),小数点前一位的位阶是0,后一位是-1
11.25 拆分为 11 和 0.25  (整数和小数部分)
整数部分解释:
11 = ... + c * 2^2 + b * 2^1 + a * 2^0
11 =  2 * (... + c * 2^1 + b) + a
11/2 =  (... + c * 2^1 + b) + a/2
注意:a , b, c ... 这些数必定是0或者1,不会大于2;
很明显将 2*(... + c * 2^1 + b) + a 看出,2*(... + c * 2^1 + b)能被2整除,余数是0;
而a无法被2整除,余数是a;所以,11/2的余数=a值=1,并且11/2的商值=(... + c * 2^1 + b)=5;
以此类推 5 = ... + c * 2^1 + b ,  5 = 2 * (...+c) + b  ,  5/2 = (... + c) + b/2
因此我们通过对整数部分除以2取余数,取完余数继续对整数部分除以2求余,直至整数为0 或 无法填充23个有效位时;从低位填二进制(低位写入  简称倒序)

11 / 2 =  5   (1)
5 / 2   =  2   (1)
2 / 2  =  1   (0)
1 / 2   = 0    (1)
整数部分为0结束
整数部分倒序写入:  1011

小数部分解释:
0.25 = a * 2^-1 + b * 2^-2 + c * 2^-3 + ...
0.25 * 2 = (a * 2^-1 + b * 2^-2 + c * 2^-3 + ...) * 2
= a + (b * 2^-1 + c * 2^-2 + ...)
同理a必定是[0,1],(b * 2^-1 + c * 2^-2 + ...)必定小于1(可以无限接近1),也就是说
a是整数,(b * 2^-1 + c * 2^-2 + ...)是小数
因此我们通过对小数部分乘以2取整,取完整继续取小数部分乘以2 直至为小数为0 或 无法填充23个有效位时(高位写入 简称顺序)

0.25 * 2 =  0.5    (0)
0.5  *2   =  1.0    (1)
小数部分为0,结束。
小数部分顺序写入:01


11.25(十进制)= 1011.01(二进制)
转IEEE754规范:1011.01 =  1.01101 * 2^3
正数符号位写入0、 指数=3+127=130(127是增量偏移)、小数位 01101 (不足23位右补0)
0 | 1000 0010 | 01101 00000 00000 00000 000
符号位(1) | 指数位(8) | 小数位(23)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/65740.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单元测试3.0+ @RunWith(JMockit.class)+mock+Expectations

Jmockit使用笔记_基本功能使用Tested_Injectable_Mocked_Expectations_jmockit.class-CSDN博客 测试框架Jmockit集合junit使用 RunWith(JMockit.class) 写在测试案例类上的注解 Tested 在测试案例中,写在我们要测试的类上面, 一般用实现类 Injectable 在测试案例中声明…

【复刻】数字化转型是否赋能企业新质生产力发展?(2015-2023年)

参照赵国庆&#xff08;2024&#xff09;的做法&#xff0c;对来自产业经济评论《企业数字化转型是否赋能企业新质生产力发展——基于中国上市企业的微观证据》一文中的基准回归部分进行复刻基于2015-2023年中国A股上市公司数据&#xff0c;实证分析企业数字化转型对新质生产力…

【数据仓库】hadoop3.3.6 安装配置

文章目录 概述下载解压安装伪分布式模式配置hdfs配置hadoop-env.shssh免密登录模式设置初始化HDFS启动hdfs配置yarn启动yarn 概述 该文档是基于hadoop3.2.2版本升级到hadoop3.3.6版本&#xff0c;所以有些配置&#xff0c;是可以不用做的&#xff0c;下面仅记录新增操作&#…

使用 CSS 的 `::selection` 伪元素来改变 HTML 文本选中时的背景颜色

定义 ::selection 伪元素&#xff1a; 在你的 CSS 文件中&#xff0c;添加 ::selection 伪元素&#xff0c;并设置 background-color 属性来改变选中文本的背景颜色。 示例代码&#xff1a; ::selection {background-color: yellow; /* 你可以根据需要更改颜色 */color: black…

【测试】接口测试

长期更新好文,建议关注收藏! 目录 接口规范接口测试用例设计postmanRequests封装接口自动化框架实例复习HTTP超文本传输协议 复习cookie+session 实现方式 1.工具 如postman ,JMeter(后者功能更全) 2.代码 python+requests / java+httpclient【高级】接口规范 传统接口 RE…

MATLAB关于集合的运算(部分)

集合运算比较两个集合中的元素&#xff0c;以找出共性或差异 i n t e r s e c t intersect intersect表示两组数据的交集 i s m e m b e r ismember ismember表示查找数据的集合成员 u n i o n union union表示两个数据集的并集 u n i q u e unique unique表示查找数据集的…

Postman[7] 内置动态参数及自定义的动态参数

postman 内置动态参数和自定义的动态参数 1.内置动态参数 格式&#xff1a;{{$参数名}} 1.1时间戳 {{$timestamp}} //生成当前时间的时间戳 1.2随机整数 {{$randomint}} //生成0-1000之间的随机数 1.3GUID字符串 {{$guid}} //生成随机GUID字符串 2.自定义动态参数 格式…

【C++】探索一维数组:从基础到深入剖析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;1. 什么是一维数组&#xff1f;一维数组的特点&#xff1a;示例 &#x1f4af;2. 一维数组的创建创建语法示例代码示例 1&#xff1a;创建整型数组示例 2&#xff1a;使用常…

为什么要用ZGC

一、为什么要用 ZGC 问题 我们有个“智慧园区”的项目,我们的下游系统“交叉带”[硬件系统]要求我们服务 60ms内返回结果&#xff0c;并且可用性要达到 99.99%。当时使用的是 G1垃圾回收器&#xff0c;单次 Young GC 40ms&#xff0c;一分钟10次&#xff0c;接口平均响应时间…

联通 路由器 创维SK-WR9551X 联通华盛VS010 组mesh 和 锐捷X32 PRO 无缝漫游

前言 联通路由器&#xff1a;联通创维SK-WR9551X&#xff0c;联通华盛VS010组mesh&#xff0c;并与锐捷X32 PRO混合组网&#xff0c;开启无限漫游。 1、mesh ≠ 无缝漫游 mesh是实现路由器快速组网的一种方式&#xff0c;通过mesh组网后可以实现无缝漫游。 mesh组网的设备要…

015-spring-动态原理、AOP的xml和注解方式

强制使用cglib动态代理 spring-AOP的使用

Nginx代理本地exe服务http为https

Nginx代理本地exe服务http为https 下载NginxNginx命令exe服务http代理为https 下载Nginx 点击下载Nginx 下载好之后是一个压缩包&#xff0c;解压放到没有中文的路径下就可以了 Nginx命令 调出cmd窗口cd到安装路径 输入&#xff1a;nginx -v 查看版本 nginx -h&#xff…

计算机网络ENSP课设--三层架构企业网络

本课程设计搭建一个小型互联网&#xff0c;并模拟Internet的典型Web服务过程。通过此次课程设计&#xff0c;可以进一步理解Internet的工作原理和协议过程&#xff0c;并提高综合知识的运用能力和分析能力。具体目标包括&#xff1a; &#xff08;1&#xff09;掌握网络拓扑的…

如何解决Eigen和CUDA版本不匹配引起的错误math_functions.hpp: No such file or directory

Apollo9针对RTX40的docker环境里的Eigen库版本是3.3.4&#xff0c;CUDA是11.8: 编译我们自己封装模型的某些component代码时没问题&#xff0c;编译一个封装occ模型的component代码时始终报错: In file included from /usr/include/eigen3/Eigen/Geometry:11:0, …

Cobalt Strike流量改造

1&#xff1a;证书设置 这里我们直接伪造成bilibili的 通过网页查看证书详情&#xff1a; 2&#xff1a;上线流量设定 这里还是比较简单的 请求路径 请求地址 这里可以依据实际情况改 比如这里直接cv 3&#xff1a;心跳流量 这里我设置的是bilibil对于内容的搜索 这里我们…

Oracle 回归分析函数使用

Oracle 回归分析函数使用 文章目录 Oracle 回归分析函数使用什么是 回归分析函数回归分析函数示例1. 分析 SAL 和 COMM 之间的回归关系2. 按部门分析 SAL 和 COMM 的关系3. 根据 SAL 预测 COMM4. 分析员工薪资与工作年限的关5. 按部门分析工作年限与薪资的关系6. 计算 REGR_AVG…

集装箱的纸箱和塑料箱识别数据集,使用YOLO,COCO JSON,PASICAL VOC XML格式标注,识别准确率高达97.5%

集装箱的纸箱和塑料箱识别数据集&#xff0c;使用YOLO&#xff0c;COCO JSON&#xff0c;PASICAL VOC XML格式标注&#xff0c;识别准确率高达97.5% 数据集分割 训练组88&#xff05; 4605图片 有效集8% 438图片 测试集4% 219图片 预处理 自动定向&#x…

STM32 高级 物联网通讯之LoRa通讯

目录 LoRa通讯基础知识 常见的3种通讯协议 远距离高速率的传输协议 近距离高速率传输技术 近距离低功耗传输技术 低功耗广域网 采用授权频段技术 非授权频段 LoRa简介 LoRa的特点 远距离 低功耗 安全 标准化 地理定位 移动性 高性能 低成本 LoRa应用 LoRa组…

【数据可视化-10】国防科技大学录取分数线可视化分析

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

基于单片机中药存放环境监测系统的实现

基于单片机中药存放环境监测系统的实现 项目开发背景 随着现代中药的广泛应用&#xff0c;中药材的存储环境对其质量有着至关重要的影响。温湿度、烟雾、火灾等环境因素&#xff0c;若不加以控制&#xff0c;将会导致中药材失效或变质。因此&#xff0c;设计一个基于单片机的…