Как преобразовать IEEE754 с плавающей точкой в фиксированную точку детерминистическим способом?
Мне нужно преобразовать 32-разрядный код IEEE754 в формат с фиксированной запятой Q19.12. Проблема состоит в том, что это должно быть сделано полностью детерминированным способом, поэтому обычное (int)(f * (1 << FRACTION_SHIFT)) не подходит, так как в нем используется недетерминированная математика с плавающей запятой. Существуют ли какие-либо "битые игры" или подобные детерминированные методы преобразования?
Редактировать: Детерминистская в этом случае принимается как: если одни и те же данные с плавающей запятой достигают абсолютно одинаковых результатов преобразования на разных платформах.
3 ответа
Хотя ответ @StephenCanon может быть верным, поскольку этот конкретный случай является полностью детерминированным, я решил остаться на более безопасной стороне и по-прежнему выполнять преобразование вручную. Вот код, с которым я закончил (спасибо @CodesInChaos за указатели на то, как это сделать):
public static Fixed FromFloatSafe(float f) {
// Extract float bits
uint fb = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0);
uint sign = (uint)((int)fb >> 31);
uint exponent = (fb >> 23) & 0xFF;
uint mantissa = (fb & 0x007FFFFF);
// Check for Infinity, SNaN, QNaN
if (exponent == 255) {
throw new ArgumentException();
// Add mantissa's assumed leading 1
} else if (exponent != 0) {
mantissa |= 0x800000;
}
// Mantissa with adjusted sign
int raw = (int)((mantissa ^ sign) - sign);
// Required float's radix point shift to convert to fixed point
int shift = (int)exponent - 127 - FRACTION_SHIFT + 1;
// Do the shifting and check for overflows
if (shift > 30) {
throw new OverflowException();
} else if (shift > 0) {
long ul = (long)raw << shift;
if (ul > int.MaxValue) {
throw new OverflowException();
}
if (ul < int.MinValue) {
throw new OverflowException();
}
raw = (int)ul;
} else {
raw = raw >> -shift;
}
return Fixed.FromRaw(raw);
}
Плавающая точка не является недетерминированной. Где вы взяли эту нелепую гипотезу?
Расширяя немного:1 << FRACTION_SHIFT
является точной степенью двойки, и, следовательно, представлен точно с плавающей запятой. Умножение с точной степенью двойки является точным (если только не произойдет переполнение / недополнение, но в этом случае в любом случае нет значимого представления с фиксированной точкой, так что вам все равно). Таким образом, единственным возможным источником округления является преобразование в целое число, которое полностью определяется C#; таким образом, результат не только детерминирован, но вы получите портативные идентичные результаты.
Если детерминизм абсолютно необходим, я бы проанализировал содержимое как целое число и сделал бы преобразование вручную.
Сначала извлеките экспоненту. Если это слишком маленький возврат 0
, если он слишком велик, выведите исключение переполнения.
Далее извлекаем знак и мантиссу (вспомним неявное ведение 1
). Если знаковый бит 1
Переверни знак мантиссы. Наконец, выполнить сдвиг по показателю степени в сочетании со смещением.
Я также написал мягкую реализацию, которая гарантирует детерминизм. Это довольно неполно, но необходимые вам части реализованы.