如何在 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) 相同?或者,在给定值10800011752025 的情况下,我如何才能对浮点计算中的差异进行规范化,以便此结果为7072(不是7071)?

注意:对于所有其他可能的整数值,它也应该与 Java 一样工作。上述情况只是 .NET 中计算可能不同的许多地方之一。

我正在使用 .NET Framework 4.5.1 和 .NET Standard 1.5,它应该在 x86x64 环境中产生相同的结果。

【问题讨论】:

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.CeilingMath.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()?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C# 中模拟邮递员的帖子

您如何在 C# 中模拟文件系统以进行单元测试?

如何正确使用 SendInput() 在 C# 中模拟鼠标输入

如何在C#中模拟没有密码的Windows身份验证登录用户

如何在 C# 中模拟 Number.intBitsToFloat()?

如何在 C# 中模拟鼠标按钮按下并按住