如何在 C# 中模拟 Number.intBitsToFloat()?
Posted
技术标签:
【中文标题】如何在 C# 中模拟 Number.intBitsToFloat()?【英文标题】:How do I Mimic Number.intBitsToFloat() in C#? 【发布时间】:2017-05-16 06:06:31 【问题描述】:我一直在疯狂尝试读取使用 Java 程序编写的二进制文件(我正在将 Java 库移植到 C# 并希望保持与 Java 版本的兼容性)。
Java 库
组件的作者选择使用float
和乘法来确定一条数据的开始/结束偏移量。不幸的是,它在 .NET 中的工作方式与在 Java 中的工作方式有所不同。在 Java 中,该库使用Float.intBitsToFloat(someInt)
,其中someInt
的值为1080001175
。
int someInt = 1080001175;
float result = Float.intBitsToFloat(someInt);
// result (as viewed in Eclipse): 3.4923456
稍后,这个数字乘以一个值来确定开始和结束位置。在这种情况下,当索引值为2025
时会出现问题。
int idx = 2025;
long result2 = (long)(idx * result);
// result2: 7072
根据我的计算器,这个计算的结果应该是7071.99984
。但在 Java 中,完全7072
在被转换为 long 之前,在这种情况下它仍然是 7072
。为了使因子完全7072
,浮点数的值必须是3.492345679012346
。
假设 float 的值实际上是 3.492345679012346
而不是 3.4923456
(Eclipse 中显示的值)是否安全?
.NET 等效项
现在,我正在寻找一种在 .NET 中获得完全相同结果的方法。但到目前为止,我只能使用 hack 读取这个文件,而且我不完全确定 hack 是否适用于 Java 库生成的 any 文件。
根据intBitsToFloat method in Java VS C#?,等效功能正在使用:
int someInt = 1080001175;
int result = BitConverter.ToSingle(BitConverter.GetBytes(someInt), 0);
// result: 3.49234557
这样计算:
int idx = 2025;
long result2 = (long)(idx * result);
// result2: 7071
转换为 long 之前的结果是 7071.99977925
,它比 Java 产生的 7072
值差。
我尝试了什么
从那里,我假设Float.intBitsToFloat(someInt)
和BitConverter.ToSingle(BitConverter.GetBytes(value), 0)
之间的数学必须存在一些差异才能获得如此不同的结果。所以,我咨询了javadocs for intBitsToFloat(int),看看我是否可以在.NET 中重现Java 结果。我最终得到:
public static float Int32BitsToSingle(int value)
if (value == 0x7f800000)
return float.PositiveInfinity;
else if ((uint)value == 0xff800000)
return float.NegativeInfinity;
else if ((value >= 0x7f800001 && value <= 0x7fffffff) || ((uint)value >= 0xff800001 && (uint)value <= 0xffffffff))
return float.NaN;
int bits = value;
int s = ((bits >> 31) == 0) ? 1 : -1;
int e = ((bits >> 23) & 0xff);
int m = (e == 0) ? (bits & 0x7fffff) >> 1 : (bits & 0x7fffff) | 0x800000;
//double r = (s * m * Math.Pow(2, e - 150));
// value of r: 3.4923455715179443
float result = (float)(s * m * Math.Pow(2, e - 150));
// value of result: 3.49234557
return result;
如您所见,结果完全与使用 BitConverter
时相同,并且在转换为 float
之前,数字 (3.4923455715179443
) 比假定的 Java 值 (3.492345679012346
) 是结果恰好为 7072
所必需的。
我尝试了this solution,但结果值完全相同,3.49234557
。
我也尝试了舍入和截断,但这当然会使所有其他与整数不太接近的值都错误。
当浮点值在整数的某个范围内时,我可以通过更改计算来解决这个问题,但是由于可能在其他地方计算非常接近整数,所以这个解决方案可能赢了不能通用。
float avg = (idx * averages[block]);
avgValue = (long)avg; // yields 7071
if ((avgValue + 1) - avg < 0.0001)
avgValue = Convert.ToInt64(avg); // yields 7072
请注意,Convert.ToInt64
函数在大多数情况下也不起作用,但在这种特殊情况下它具有舍入的效果。
问题
如何在 .NET 中创建一个函数,该函数返回的结果完全与 Java 中的Float.intBitsToFloat(int)
相同?或者,在给定值1080001175
和2025
的情况下,我如何才能对浮点计算中的差异进行规范化,以便此结果为7072
(不是7071
)?
注意:对于所有其他可能的整数值,它也应该与 Java 一样工作。上述情况只是 .NET 中计算可能不同的许多地方之一。
我正在使用 .NET Framework 4.5.1 和 .NET Standard 1.5,它应该在
x86
和x64
环境中产生相同的结果。
【问题讨论】:
intBitsToFloat
是一个本地方法,它在 C 中调用 Java_java_lang_Float_intBitsToFloat
方法,see Float.c contents。它将jint
转换为long
并返回jfloat
,与System.Single
相比,jfloat
本身可能使用不同的舍入机制。更奇怪的是,C# 中的 float
在使用与 Java 实现相同的代码转换为 long
期间向下舍入。
我也遇到了四舍五入的问题。但是,我能够使用(long)BigInteger.Divide(BigInteger.Multiply(new BigInteger(idx), new BigInteger(10000000 * averages[block])), new BigInteger(10000000))
解决问题。如果有某种方法可以在 .NET 中调用本机 C 函数,那么这可能对解决这个问题大有帮助。
在这种情况下 Math.Ceiling
或 Math.Floor
会有所帮助,因为它总是会分别产生下一个更高/更低的整数。 (long)Math.Ceiling(7071.1)
总是产生 7072
只需在 .NET 中执行此操作:long result2 = (long)(float)(idx * result);
它在生成的 IL 中添加了一个 conv.r4
操作码,因此在计算堆栈的某处强制实现浮点实现(甚至 Visual Studio 将其报告为“不必要的演员“...)。我想这是一个 jit 优化。也许是一个错误?如果您为 .NET 2.0 编译,我不会发生...
@SimonMourier - 我尝试了演员(long)(float)(idx * result);
,它似乎解决了这个问题。我需要做更多的测试来验证它在 .NET 4.5.1 和 .NET Standard 1.5 中可以跨 x86 和 x64 工作,但这看起来很有希望。请添加您的评论作为答案,以便我可以接受此解决方案是否有效。
【参考方案1】:
C#和Java(以及其他任何体面的编程平台)中4字节浮点数的定义都是基于IEEE standards,所以二进制格式是一样的。
所以,它应该可以工作。事实上它确实有效,但仅适用于 X64 目标(我早期关于 .NET 2 和 4 的 cmets 可能是错的或对的,我无法真正测试旧平台二进制文件)。
如果您希望它适用于所有目标,则必须像这样定义它:
long result2 = (long)(float)(idx * result);
如果您查看生成的 IL,它会在乘法后添加一个补充的 conv.r4 操作码。我猜这会强制在编译的 x86 代码中实现浮点数。我想这是一个 jit 优化问题。
我对 jit 优化知之甚少,无法确定它是否是错误。有趣的是,Visual Studio 2017 IDE 甚至将演员表 (float)
文本变灰,并报告说演员表是“多余的”或“不必要的”,所以它闻起来不太好。
【讨论】:
关于有趣的事情:int
(idx * result
) 按照docs.microsoft.com/en-us/dotnet/articles/csharp/… 隐式转换为float
,另见备注。【参考方案2】:
仅供参考 - 现在直接添加到 .net 中。
int myFloat = BitConverter.Int32BitsToSingle( myInt );
所以...(如问题)
int someInt = 1080001175;
float result = BitConverter.Int32BitsToSingle(someInt);
返回 3.49234557(几乎和 Eclipse 一样)
【讨论】:
以上是关于如何在 C# 中模拟 Number.intBitsToFloat()?的主要内容,如果未能解决你的问题,请参考以下文章
如何正确使用 SendInput() 在 C# 中模拟鼠标输入