【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,一经查实,立即删除!

相关文章

再见24你好25

现在是2024年12月31日。 愿我们都有我与我周旋久&#xff0c;宁做我的勇气。一、验证24年的期望事件 1、 1.本身说要出去跑步&#xff0c;但是膝盖伤了&#xff0c;可能以后都跑不成了 2.linux内存方面的东西看过一遍&#xff0c;自己也写了博客&#xff0c;但还是缺乏真正的…

用 Python 从零开始创建神经网络(十八):模型对象(Model Object)

模型对象&#xff08;Model Object&#xff09; 引言到目前为止的完整代码&#xff1a; 引言 我们构建了一个可以执行前向传播、反向传播以及精度测量等辅助任务的模型。通过编写相当多的代码并在一些较大的代码块中进行修改&#xff0c;我们实现了这些功能。此时&#xff0c;…

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

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

npm install 安装选项 -d -s -g

在使用 npm install 时&#xff0c;-d、-g 和 -s 是不同的选项&#xff0c;它们分别代表不同的安装模式或行为。以下是它们的详细解释&#xff1a; 1. -d&#xff1a;--save-dev 含义&#xff1a;将包安装为开发依赖&#xff08;devDependencies&#xff09;。使用场景&#…

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

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

Vue axios 异步请求,请求响应拦截器

在 Vue.js 中使用 axios 进行网络请求是非常常见的做法&#xff0c;因为它提供了比原生的 Fetch API 更丰富的功能&#xff0c;并且更易于处理错误和配置。结合 Axios 的拦截器功能&#xff0c;你可以对所有的请求或响应进行预处理&#xff0c;比如添加认证头信息、统一处理错误…

px、em、rem,vw区别介绍

在网页设计中&#xff0c;px、em、rem 和 vw 都是常用的 CSS 单位&#xff0c;它们各自有不同的用途和特性。下面是这些单位的详细介绍及其区别&#xff1a; 1. px&#xff08;像素&#xff09; 定义&#xff1a; px 是最常用的绝对单位&#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…

【Kafka 消息队列深度解析与应用】

Kafka 消息队列深度解析与应用 一、Kafka 概述 &#xff08;一&#xff09;产生背景 Kafka 最初是由 LinkedIn 开发&#xff0c;旨在解决其内部海量数据的实时传输问题。在现代大数据环境下&#xff0c;企业需要处理海量的数据流入和流出&#xff0c;包括用户的行为数据、系…

【测试】接口测试

长期更新好文,建议关注收藏! 目录 接口规范接口测试用例设计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表示查找数据集的…

与你共度的烟火日常

见过不少人、经过不少事、也吃过不少苦&#xff0c;感悟世事无常、人心多变&#xff0c;靠着回忆将往事串珠成链&#xff0c;聊聊感情、谈谈发展&#xff0c;我慢慢写、你一点一点看...... 我和她一起收拾完屋子&#xff0c;忙完已经中午了。她说&#xff1a;“咱们去趟超市吧&…

【每日学点鸿蒙知识】无障碍、getLastLocation、蓝牙问题、卡片大小、关系型数据库等

1、是否有类似无障碍辅助相关的API&#xff1f; 场景描述&#xff1a;锁机app&#xff0c;需要通过无障碍能力辅助检测当前正在打开的app&#xff0c;以及模拟用户操作&#xff0c; 关闭用户想要屏蔽的app 可参考&#xff1a;https://developer.huawei.com/consumer/cn/doc/h…

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;接口平均响应时间…

ffmpeg 编译+ libx264

编译 libx264 将 libx264 生成结果拷贝到 msys64 的 usr\local 目录下。这样在 msys2_shell 中就可以使用 /usr/local 来找到这个路径了。 编译不设置 prefix&#xff0c;默认将文件拷贝到 /usr/local 编译 ffmpeg libx264 配置 pkg-config&#xff0c;不然编译找不到 libx26…

联通 路由器 创维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的使用