Быстрый вход в C ++ float ... есть ли зависимости платформы от этого кода? - PullRequest
4 голосов
/ 24 марта 2010

Выполняя поиск в Интернете, я нашел следующую процедуру для вычисления знака с плавающей точкой в ​​формате IEEE. Это также может быть легко увеличено до двойного.

// returns 1.0f for positive floats, -1.0f for negative floats, 0.0f for zero
inline float fast_sign(float f) {
    if (((int&)f & 0x7FFFFFFF)==0) return 0.f; // test exponent & mantissa bits: is input zero?
    else {
        float r = 1.0f;
        (int&)r |= ((int&)f & 0x80000000); // mask sign bit in f, set it in r if necessary
        return r;
    }
}

( Источник : `` Быстрый знак для 32-разрядных операций с плавающей запятой '', Питер Шоффхаузер)

Я устал использовать эту подпрограмму из-за битовых двоичных операций. Мне нужен мой код для работы на машинах с разным порядком байтов, но я не уверен, насколько это указано в стандарте IEEE, так как я не смог найти самую последнюю версию, опубликованную в этом году. Может кто-нибудь сказать мне, будет ли это работать, независимо от порядка байтов машины?

Спасибо, Patrick

Ответы [ 2 ]

10 голосов
/ 24 марта 2010

Как вы думаете, fabs() и fabsf() реализованы в вашей системе, или в этом отношении сравнения с константой 0? Если это не побитовые операции, это вполне возможно, потому что авторы компиляторов не думают, что это будет быстрее.

Проблемы с переносимостью этого кода:

  1. float и int могут не иметь одинаковую последовательность или даже одинаковый размер. Следовательно, маски тоже могут быть неправильными.
  2. float не может быть IEEE представлением
  3. Вы нарушаете строгие правила наложения имен. Компилятору разрешается предполагать, что указатель / ссылка на float и указатель / ссылка на int не могут указывать на одну и ту же ячейку памяти. Так, например, стандарт не гарантирует, что r инициализируется с 1.0, прежде чем он будет изменен в следующей строке. Это может изменить порядок операций. Это не пустое предположение, и в отличие от (1) и (2) оно не определено, не определено реализацией, поэтому вы не можете просто найти его для своего компилятора. При достаточной оптимизации я видел, что GCC пропускает инициализацию переменных с плавающей точкой, на которые ссылаются только через указатель типа.

Сначала я бы сделал очевидную вещь и проверил выдаваемый код. Только если это кажется хитрым, стоит подумать о чем-то другом. У меня нет особых причин думать, что я знаю больше о побитовом представлении чисел, чем мой компилятор; -)

inline float fast_sign(float f) {
    if (f > 0) return 1;
    return (f == 0) ? 0 : -1;
    // or some permutation of the order of the 3 cases
}

[Редактировать: на самом деле, GCC делает что-то вроде еды даже с -O3. Передаваемый код не обязательно медленный, но он использует операции с плавающей запятой, поэтому неясно, что он быстрый. Поэтому следующим шагом является тестирование, проверка того, является ли альтернатива более быстрой на любом компиляторе, на который вы можете положиться, и если это так, сделайте это тем, что люди, портирующие ваш код, могут включить с помощью #define или чем-то еще, в соответствии с результатами их собственный тест.]

3 голосов
/ 24 марта 2010

Не забывайте, что для перемещения значения с плавающей запятой из регистра FPU в регистр целых чисел требуется запись в RAM, а затем чтение.

С помощью кода с плавающей запятой вам всегда будет лучше, если смотреть на большую картинку:

Some floating point code
Get sign of floating point value
Some more floating point code

В приведенном выше сценарии использование FPU для определения знака будет быстрее, так как не будет служебных данных записи / чтения 1 . Intel FPU может делать:

FLDZ
FCOMP

, который устанавливает флаги кода состояния для > 0, < 0 и == 0 и может использоваться с FCMOVcc.

Вставка вышеупомянутого в хорошо написанный код FPU побьет любые целочисленные битовые манипуляции и не потеряет точность 2 .

Примечания:

  1. В Intel IA32 реализована оптимизация чтения после записи, при которой он не будет ожидать поступления данных в ОЗУ / кэш, а просто использует значение напрямую. Он по-прежнему делает кэш недействительным, так что есть эффект наклона.
  2. Intel FPU имеет внутреннюю длину 80 бит, число с плавающей запятой - 32, а число с удвоением - 64, поэтому преобразование в число с плавающей запятой / двойная для повторной загрузки в виде целого числа приведет к потере точности. Это важные биты, так как вы ищете переходы около 0.
...