首页 > 解决方案 > 为什么在转换浮点数和字节时,BitConverter 似乎返回不正确的结果?

问题描述

我正在使用 C# 并尝试将四个字节打包成一个浮点数(上下文是游戏开发,其中 RGBA 颜色被打包成一个值)。为此,我正在使用BitConverter,但某些转换似乎会导致不正确的字节。举个例子(使用 bytes 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)。

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

任何人都可以阐明这里发生的事情吗?

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

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

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

这在所有情况下都可以在 C# 中实现吗?

标签: c#endiannessbitconverter

解决方案


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

浮点位布局的可视化表示。

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

例如,字节0, 0, 129, 255(以相反的顺序编码,我认为由于字节序)将值129放在第二个字节(绿色框)中。129在二进制中是10000001,所以翻转它的第二位给出11000001,这是193(正是我在原始示例中看到的)。这种相同的模式(编码字节的值发生了变化)适用于范围内的所有字节129-191。字节128和更低不是 NaN,而字节192和更高NaN,但不要修改它们的值,因为它们的第二位(放置在最高阶尾数位)已经是 1。

所以这回答了为什么会发生这种行为,但在我看来,还有两个问题:

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

第一个问题的答案似乎是否定的(如果我不了解,我会修改这个答案)。但是,请务必注意,此行为在所有 .NET 版本中似乎并不一致。在我的计算机上,我尝试的每个 .NET Framework 版本(从 4.8.0 开始,然后向下工作)都会转换NaN (即我的编码字节已更改)。NaN 在 .NET Core 3 和 .NET 5 中似乎没有被转换(即我的编码字节没有改变)(我没有测试每个可用版本)。另外,有朋友在.NET Framework 4.7.2上也能运行同样的示例代码,出乎意料的是字节数并没有在他的机器上修改。不同 C# 运行时的内部结构不是我的专业领域,但足以说明版本和计算机之间存在差异。

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


推荐阅读