Я с ума сходил, пытаясь прочитать двоичный файл, написанный с использованием Java-программы (я переношу библиотеку Java на С# и хочу поддерживать совместимость с версией 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, прежде чем он будет отброшен до длинного, и в этом случае он все еще 7072. Для того чтобы коэффициент был точно 7072, значение float должно было бы быть 3.492345679012346.
Можно ли предположить, что значение float фактически 3.492345679012346 вместо 3.4923456 (значение, показанное в Eclipse)?
Эквивалент .NET
Теперь я ищу способ получить тот же результат в .NET. Но до сих пор я мог прочитать этот файл только с помощью взлома, и я не совсем уверен, что хак будет работать для любого файла, который создается библиотекой на Java.
В соответствии с методом intBitsToFloat в Java VS С#? эквивалентная функциональность использует:
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, который лишен значения 7072, которое дает Java.
Что я пробовал
Оттуда я предположил, что в математике между Float.intBitsToFloat(someInt) и BitConverter.ToSingle(BitConverter.GetBytes(value), 0) должна быть какая-то разница, чтобы получать такие разные результаты. Итак, я посоветовался с javadocs для intBitsToFloat (int), чтобы увидеть, могу ли я воспроизвести результаты Java в .NET. Я закончил с:
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.
Я попробовал это решение, но результирующее значение точно такое же, 3.49234557.
Я также пытался округлить и обрезать, но, конечно, это делает все другие значения, которые не очень близки к целому числу, неверны.
Мне удалось взломать это, изменив вычисление, когда значение float находится в пределах определенного диапазона целого числа, но поскольку могут быть другие места, где расчет очень близок к целому числу, это решение, вероятно, выиграло Работает повсеместно.
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, которая возвращает точно такой же результат, что и Float.intBitsToFloat(int) в Java? Или, как я могу иначе нормализовать различия в вычислении float, чтобы этот результат был 7072 (not 7071) с учетом значений 1080001175 и 2025?
Примечание. Он должен работать так же, как и Java для всех других возможных значений целого числа. Вышеприведенный случай является лишь одним из потенциально многих мест, где вычисления отличаются в .NET.
Я использую .NET Framework 4.5.1 и .NET Standard 1.5, и он должен давать те же результаты в средах
x86иx64.