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

14

我一直在努力阅读一个用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中它在转换为long类型之前就是确切的7072,在这种情况下它仍然是7072。为了使因子确切的7072,浮点值必须是3.492345679012346

可以安全地假设浮点数的值实际上是3.492345679012346而不是3.4923456(在Eclipse中显示的值)吗?

.NET等效性

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

根据Java中的intBitsToFloat方法与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

转换为长整型之前的结果为7071.99977925,这比Java给出的值7072要小。

尝试过的方法

于是我假设在Float.intBitsToFloat(someInt)BitConverter.ToSingle(BitConverter.GetBytes(value), 0)之间存在某些数学差异,以获得如此不同的结果。因此,我查阅了intBitsToFloat(int)的javadocs,看看能否在.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之前,该数字比所需的Java值(3.492345679012346) 要低得多,后者需要结果正好为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
}

请注意,Convert.ToInt64 函数在大多数情况下也不起作用,但在这种特殊情况下它会产生四舍五入的效果。

问题

如何在.NET中编写一个函数,返回与Java中的 Float.intBitsToFloat(int) 完全相同 的结果?或者,如何规范化浮点计算中的差异,使得给定值 10800011752025 时,结果是 7072 而不是 7071?

注意:对于所有其他可能的整数值,它应该与Java的表现一致。上面的情况只是其中可能有许多计算方式不同的情况之一。

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


intBitsToFloat是一个本地方法,它调用C中的Java_java_lang_Float_intBitsToFloat方法,请参见Float.c内容。它将jint转换为long并返回jfloat,而jfloat本身可能与System.Single使用不同的舍入机制。更奇怪的是,在使用与Java实现相同的代码进行转换为long时,C#中的结果float会向下舍入。 - Tetsuya Yamamoto
我也遇到了向下取整的问题。不过,我通过使用(long)BigInteger.Divide(BigInteger.Multiply(new BigInteger(idx), new BigInteger(10000000 * averages[block])), new BigInteger(10000000))成功地解决了这个问题。如果有一种方法可以在.NET中调用本机C函数,那么这可能会大大解决这个问题。 - NightOwl888
1
在.NET中,只需这样做:long result2 = (long)(float)(idx * result); 它会在生成的IL中添加一个 conv.r4 操作码,从而在计算堆栈中某个地方强制进行浮点数实现(即使Visual Studio将其报告为“不必要的转换”...)。我想这是jit优化。也许是一个bug?如果你编译成.NET 2.0,它就不会发生... - Simon Mourier
很遗憾,我认为没有直接调用本地C方法的方法,但是在之前提到的Float.c中呈现的union在C#中有类似的实现,使用StructLayout(LayoutKind.Explicit)FieldOffset(n)属性来声明struct(适用于简单类型,请参见https://dev59.com/YXVC5IYBdhLWcg3w-mZO)。有趣的是,在`jni.h`中,`jfloat`本身被定义为`typedef float jfloat,这意味着jfloat本身只是C类型float`的别名。 - Tetsuya Yamamoto
1
@SimonMourier - 我尝试了强制转换 (long)(float)(idx * result);,它似乎解决了这个问题。我需要进行更多的测试来验证它在 .NET 4.5.1 和 .NET Standard 1.5 上是否适用于 x86 和 x64,但这看起来很有前途。请将您的评论作为答案添加,以便我可以接受此解决方案。 - NightOwl888
显示剩余3条评论
2个回答

5
在C#和Java(以及任何其他体面的编程平台)中,4字节浮点数的定义基于IEEE标准,因此二进制格式是相同的。因此,它应该可以工作。实际上它确实可以工作,但只适用于X64目标(我之前关于.NET 2和4的评论可能是对也可能是错,我无法真正测试旧平台二进制文件)。如果您希望它适用于所有目标,则必须像这样定义:
long result2 = (long)(float)(idx * result);

如果您查看生成的 IL,会在乘法之后添加一个补充 conv.r4 操作码。我猜这会在编译的 x86 代码中强制实现浮点数。我认为这可能是 jit 优化问题。
我不了解足够的 jit 优化知识来确定它是否是错误。有趣的是,甚至 Visual Studio 2017 IDE 都会将转换 (float) 文本变为灰色,并将该转换报告为“冗余”或“不必要”,所以这不是好的迹象。

关于有趣的事情:根据 https://learn.microsoft.com/en-us/dotnet/articles/csharp/language-reference/keywords/implicit-numeric-conversions-table 和备注所述,int (idx * result) 可以隐式转换为 float - Caramiriel

-1

知道了 - 现在已经直接添加到 .net 中了。

int myFloat = BitConverter.Int32BitsToSingle( myInt );

所以...(作为问题的答复)

int someInt = 1080001175;
float result = BitConverter.Int32BitsToSingle(someInt);

返回3.49234557(几乎与Eclipse相同)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接