我一直在疯狂地尝试读取使用Java程序编写的二进制文件(我将Java库移植到C#并希望保持与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
。
3.492345679012346
而不是3.4923456
(Eclipse中显示的值)是否安全?现在,我正在寻找一种在.NET中获得完全相同结果的方法。但到目前为止,我只能使用hack读取这个文件,而且我不完全确定hack适用于由Java库生成的任何文件。
根据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
之前,数字相当低({{ 1}})而不是假定的(3.4923455715179443
)Java值,结果恰好是3.492345679012346
。
我尝试this solution,但结果值完全相同,7072
。
我也尝试过舍入和截断,但当然这会使所有其他值与整数不太接近。
当浮点值在整数的某个范围内时,我能够通过改变计算来破解这一点,但由于可能存在计算非常接近整数的其他地方,这个解决方案可能会赢得& #39;普遍工作。
3.49234557
请注意,float avg = (idx * averages[block]);
avgValue = (long)avg; // yields 7071
if ((avgValue + 1) - avg < 0.0001)
{
avgValue = Convert.ToInt64(avg); // yields 7072
}
函数在大多数情况下都不起作用,但在这种特殊情况下它具有舍入效果。
如何在.NET中创建一个函数,使得与Java中的Convert.ToInt64
完全相同?或者,我如何以其他方式规范浮点计算中的差异,因此给定值Float.intBitsToFloat(int)
和7072
时,此结果为7071
(不是1080001175
)?
注意:对于所有其他可能的整数值,它应该与Java相同。以上情况只是.NET中计算不同的许多地方之一。
我使用的是.NET Framework 4.5.1和.NET Standard 1.5,它应该在
2025
和x86
环境中产生相同的结果。
答案 0 :(得分:5)
C#和Java(以及任何其他体面的编程平台)中4字节浮点数的定义基于IEEE standards,因此二进制格式是相同的。
所以,它应该工作。事实上它确实有效,但仅适用于X64目标(我之前关于.NET 2和4的评论可能是错误或正确的,我无法真正测试旧的平台二进制文件。)
如果您希望它适用于所有目标,您必须像这样定义:
long result2 = (long)(float)(idx * result);
如果查看生成的IL,它会在乘法后添加一个补充conv.r4操作码。我想这会在编译的x86代码中强制浮点数实现。我想这是一个jit优化问题。
我对jit优化知之甚少,以确定它是否是一个bug。有趣的是,Visual Studio 2017 IDE甚至会将演员(float)
文本和报告视为“冗余”或“不必要”,因此它闻起来不太好。
答案 1 :(得分:-1)
仅供参考 - 现在直接添加到 .net 中。
int myFloat = BitConverter.Int32BitsToSingle( myInt );
所以...(如问题所示)
int someInt = 1080001175;
float result = BitConverter.Int32BitsToSingle(someInt);
返回 3.49234557(与 Eclipse 几乎相同)