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

Nig*_*888 14 .net c# binaryfiles

我一直在疯狂地尝试读取使用Java程序编写的二进制文件(我将Java库移植到C#并希望保持与Java版本的兼容性).

Java库

该组件的作者选择使用float乘法和乘法来确定一段数据的开始/结束偏移.不幸的是,它在.NET中的工作方式与Java相比有所不同.在Java中,库使用Float.intBitsToFloat(someInt)其中的价值someInt1080001175.

int someInt = 1080001175;
float result = Float.intBitsToFloat(someInt);
// result (as viewed in Eclipse): 3.4923456
Run Code Online (Sandbox Code Playgroud)

之后,此数字乘以一个值以确定开始和结束位置.在这种情况下,索引值为时会出现问题2025.

int idx = 2025;
long result2 = (long)(idx * result);
// result2: 7072
Run Code Online (Sandbox Code Playgroud)

根据我的计算器,这个计算的结果应该是7071.99984.但是在Java中它恰好 7072在它被投入很长时间之前,在这种情况下它仍然存在7072.为了使因子准确 7072,浮动的值必须是3.492345679012346.

假设float的值实际上是3.492345679012346代替3.4923456(Eclipse中显示的值)是否安全?

.NET等效

现在,我正在寻找一种在.NET中获得完全相同结果的方法.但到目前为止,我只能使用hack读取这个文件,而且我并不完全确定hack适用于Java中的库生成的任何文件.

根据Java VS C#中的intBitsToFloat方法?,等效功能使用:

int someInt = 1080001175;
int result = BitConverter.ToSingle(BitConverter.GetBytes(someInt), 0);
// result: 3.49234557
Run Code Online (Sandbox Code Playgroud)

这使得计算:

int idx = 2025;
long result2 = (long)(idx * result);
// result2: 7071
Run Code Online (Sandbox Code Playgroud)

在转换为long之前的结果是7071.99977925,这与7072Java产生的价值相悖.

我试过的

从那里,我认为必须有之间的数学差一些Float.intBitsToFloat(someInt),并BitConverter.ToSingle(BitConverter.GetBytes(value), 0)接收这些不同的结果.所以,我查询了intBitsToFloat(int)javadoc,看看我是否可以在.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;
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,结果与使用时完全相同BitConverter,并且在转换为float数字之前,该数字3.4923455715179443要比3.492345679012346结果准确所需的()的假定Java值低得多()7072.

我尝试了这个解决方案,但结果值完全相同,3.49234557.

我也尝试过舍入和截断,但当然这使得所有其他值都不是非常接近整数就错了.

我可以通过在浮点值在整数的某个范围内时更改计算来破解这一点,但是因为可能有其他地方计算非常接近整数,这个解决方案可能不起作用普遍.

float avg = (idx * averages[block]);
avgValue = (long)avg; // yields 7071
if ((avgValue + 1) - avg < 0.0001)
{
    avgValue = Convert.ToInt64(avg); // yields 7072
}
Run Code Online (Sandbox Code Playgroud)

请注意,该Convert.ToInt64函数在大多数情况下也不起作用,但在这种特殊情况下它具有舍入效果.

如何在.NET中创建一个返回与Java 完全相同的结果的函数Float.intBitsToFloat(int)?或者,我怎样才能将浮点计算中的差异归一化,因此这个结果是7072(不7071)给定值10800011752025

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

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

Sim*_*ier 5

C#和Java(以及任何其他体面的编程平台)中4字节浮点数的定义基于IEEE标准,因此二进制格式是相同的.

所以,它应该工作.事实上它确实有效,但仅适用于X64目标(我之前关于.NET 2和4的评论可能是错误或正确的,我无法真正测试旧的平台二进制文件).

如果您希望它适用于所有目标,您必须像这样定义它:

long result2 = (long)(float)(idx * result);
Run Code Online (Sandbox Code Playgroud)

如果查看生成的IL,它会在乘法后添加一个补充的conv.r4操作码.我想这会在编译的x86代码中强制浮点数实现.我想这是一个jit优化问题.

我对jit优化知之甚少,以确定它是否是一个bug.有趣的是Visual Studio 2017 IDE甚至会将演员(float)文本和报告视为"多余"或"不必要",因此它闻起来不太好.