Как преобразовать 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Переверни знак мантиссы. Наконец, выполнить сдвиг по показателю степени в сочетании со смещением.

Я также написал мягкую реализацию, которая гарантирует детерминизм. Это довольно неполно, но необходимые вам части реализованы.

Другие вопросы по тегам