c# - 为什么 BitConverter 在转换 float 和字节时似乎返回不正确的结果?

标签 c# endianness bitconverter

我正在使用 C# 工作,并尝试将四个字节打包到一个浮点中(背景是游戏开发,其中 RGBA 颜色被打包到单个值中)。为此,我使用 BitConverter ,但某些转换似乎会导致错误的字节。采取以下示例(使用字节 0, 0, 129, 255 ):

var before = new [] { (byte)0, (byte)0, (byte)129, (byte)255 };
var f = BitConverter.ToSingle(before, 0); // Results in NaN
var after = BitConverter.GetBytes(f); // Results in bytes 0, 0, 193, 255

使用https://www.h-schmidt.net/FloatConverter/IEEE754.html ,我验证了我开始的四个字节( 0, 0, 129, 255 ,相当于二进制 00000000000000001000000111111111 )代表浮点值 4.66338115943e-41 。通过翻转字节序(二进制 11111111100000010000000000000000 ),我得到 NaN (与上面代码中的 f 匹配)。但是当我将该浮点转换回字节时,我得到 0, 0, 193, 255 (注意 193 当我期待 129 时)。

奇怪的是,使用字节 0, 0, 128, 255 运行相同的示例是正确的(浮点值 f 变为 -Infinity ,然后转换回字节再次产生 0, 0, 128, 255 )。鉴于这一事实,我怀疑NaN是相关的。

谁能解释一下这里发生的事情吗?

更新:问题 Converting 2 bytes to Short in C#被列为重复项,但这是不准确的。该问题试图将字节转换为一个值(在这种情况下,将两个字节转换为一个短字节),并且不正确的字节序给出了意外的值。就我而言,实际的浮点值是无关紧要的(因为我没有使用转换后的值作为浮点值)。相反,我尝试通过首先转换为 float ,然后再转换回来,将四个字节直接有效地重新解释为 float 。如图所示,这种来回有时会返回与我发送的字节不同的字节。

第二次更新:我将简单地回答我的问题。正如彼得杜尼霍评论,BitConverter永远不会修改您传入的字节,而只是将它们复制到新的内存位置并重新解释结果。但是,正如我的示例所示,可以发送四个字节 ( 0, 0, 129, 255 ),这些字节在内部复制并重新解释为 float ,然后将该 float 转换回与原始不同的字节(0, 0, 193, 255)。

BitConverter 中经常提到字节序。 。然而,在这种情况下,我认为字节顺序并不是根本问题。当我调用BitConverter.ToSingle时,我传入一个四个字节的数组。这些字节表示一些转换为 float 的二进制(32 位)。通过在函数调用之前更改字节序,我所做的就是更改发送到函数中的位。无论这些位的如何,都应该可以将它们转换为浮点型(也是32位),然后将浮点型转换回我发送的相同位>。正如我的示例所示,使用字节 0, 0, 129, 255 (二进制 00000000000000001000000111111111 )产生浮点值。我想获取该值(由这些位表示的 float )并将其转换为原始的四个字节。

在所有情况下这在 C# 中都是可能的吗?

最佳答案

经过研究、实验以及与 friend 的讨论,这种行为(在与 float 转换时字节发生变化)的根本原因似乎是 signaling vs. quiet NaNs (正如汉斯·帕桑特在评论中也指出的那样)。我不是信号和安静 NaN 方面的专家,但据我了解,安静 NaN 将尾数的最高位设置为 1,而信号 NaN 将该位设置为零。请参阅下图(取自 https://www.h-schmidt.net/FloatConverter/IEEE754.html )以供引用。我在每组八位周围绘制了四个彩色框,以及一个指向最高阶尾数位的箭头。

Visual representation of a float's bit layout.

当然,我发布的问题不是关于浮点位布局或信号与安静 NaN 的问题,而是简单地询问为什么我的编码字节似乎被修改了。答案是 C# 运行时(或者至少我假设是 C# 运行时)在内部将所有信号 NaN 转换为安静,这意味着在该处编码的字节位置的第二位从零交换为一

例如,字节 0, 0, 129, 255 (以相反的顺序编码,我认为是由于字节顺序)将值 129 放在第二个字节中(绿色框)。二进制中的 12910000001,因此翻转其第二位会得到 11000001,即 193 (正是我所看到的在我原来的例子中)。这种相同的模式(其值已更改的编码字节)适用于 129-191 范围内的所有字节(含)。字节 128 及更低的字节不是 NaN,而字节 192 及更高的字节 NaN,但不会修改它们的值,因为它们的第二位(位于最高阶尾数位)已经是一。

这样就回答了为什么会发生这种行为,但在我看来,还剩下两个问题:

  1. 是否可以在 C# 中禁用此行为(将信号 NaN 转换为安静)?
  2. 如果没有,解决方法是什么?

第一个问题的答案似乎是(如果我了解到其他情况,我会修改这个答案)。但是,需要注意的是,这种行为在所有 .NET 版本中并不是一致的。在我的计算机上,我尝试过的每个 .NET Framework 版本(从 4.8.0 开始,然后向下工作)都会转换 NaN(即我的编码字节已更改)。 NaN 在 .NET Core 3 和 .NET 5 中似乎没有被转换(即我的编码字节没有改变)(我没有测试每个可用版本)。此外,一位 friend 能够在 .NET Framework 4.7.2 上运行相同的示例代码,令人惊讶的是,字节在他的计算机上没有被修改。不同 C# 运行时的内部结构不是我的专业领域,但足以说明版本和计算机之间存在差异。

第二个问题的答案是,正如其他人所建议的那样,完全避免浮点转换。相反,每组四个字节(在我的例子中代表 RGBA 颜色)可以用整数进行编码,也可以直接添加到字节数组中。

关于c# - 为什么 BitConverter 在转换 float 和字节时似乎返回不正确的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67453428/

相关文章:

c# - 如何将字节数组转换为通用数组?

c# - 在 C# 中执行包含 SOURCE 调用的 SQL 脚本

c# - 监视给定类的属性值变化的快速方法?

C# ContextMenuStrip 项目属性!

c# - 如何将泛型 List<T> 转换为基于接口(interface)的 List<T>

c++ - 从 C/C++ 中的 64 位值中获取 32 位字而不用担心字节序

c++ - 为什么浮点字节交换不同于整数字节交换?

linux - Endianness 是一种属性、硬件还是软件?