Можно ли оптимизировать static_cast <float>из double, назначенного для double? - PullRequest
0 голосов
/ 02 ноября 2018

Я наткнулся на функцию, которая я считаю ненужной, и, как правило, меня пугает:

float coerceToFloat(double x) {
    volatile float y = static_cast<float>(x);
    return y;
}

Который затем используется так:

// double x
double y = coerceToFloat(x);

Отличается ли это от того, что ты делаешь?

double y = static_cast<float>(x);

Намерение состоит в том, чтобы просто раздвинуть двойное число до одинарной точности. Он пахнет чем-то написанным из-за крайней паранойи.

Ответы [ 4 ]

0 голосов
/ 02 ноября 2018

Независимо от того, разрешено ли такое приведение в действие, оно происходит , и изменчивое назначение останавливает его.

Например, компиляция MSVC для 32-битной (то есть с использованием x87) с /Ox /fp:fast:

_x$ = 8                                       ; size = 8
float uselessCast(double) PROC                         ; uselessCast
        fld     QWORD PTR _x$[esp-4]
        ret     0
float uselessCast(double) ENDP                         ; uselessCast

_y$ = 8                                       ; size = 4
_x$ = 8                                       ; size = 8
float coerceToFloat(double) PROC                   ; coerceToFloat
        fld     QWORD PTR _x$[esp-4]
        fstp    DWORD PTR _y$[esp-4]
        fld     DWORD PTR _y$[esp-4]
        ret     0
float coerceToFloat(double) ENDP 

Где uselessCast такое же, как ниже, а coerceToFloat как в вопросе.

float uselessCast(double x)
{
    return static_cast<float>(x);
}

Аналогично, GCC и Clang с -O3 -ffast-math -m32 -mfpmath=387

uselessCast(double):
    fld     QWORD PTR [esp+4]
    ret
coerceToFloat(double):
    sub     esp, 20
    fld     QWORD PTR [esp+24]
    fstp    DWORD PTR [esp+12]
    fld     DWORD PTR [esp+12]
    add     esp, 20
    ret

ссылка Годболта для всего вышеперечисленного

Конечно, вы можете утверждать, что с /fp:fast или -ffast-math вы все равно не должны ожидать чего-либо от арифметики с плавающей запятой, но вам это может понадобиться и все же иметь возможность отбрасывать избыточную точность.

0 голосов
/ 02 ноября 2018

После комментария @NathanOliver - компиляторам разрешено выполнять вычисления с плавающей запятой с большей точностью, чем типы операндов. Обычно на x86 это означает, что они делают все как 80-битные значения, потому что это наиболее эффективно в оборудовании. Только когда значение сохранено, оно должно быть возвращено к фактической точности типа. И даже в этом случае большинство компиляторов по умолчанию будут выполнять оптимизации, которые нарушают это правило, потому что форсирование этого изменения в точности замедляет операции с плавающей запятой. В большинстве случаев это нормально, потому что дополнительная точность не вредна. Если вы сторонник, вы можете использовать переключатель командной строки, чтобы заставить компилятор соблюдать это правило хранения, и вы можете увидеть, что ваши вычисления с плавающей запятой значительно медленнее.

В этой функции маркировка переменной volatile сообщает компилятору, что он не может исключить сохранение этого значения; это, в свою очередь, означает, что оно должно снизить точность входного значения, чтобы соответствовать типу, в котором оно хранится. Поэтому есть надежда, что это приведет к усечению.

И, нет, написание приведения вместо вызова этой функции - не одно и то же, потому что компилятор (в своем несоответствующем режиме) может пропустить присваивание до y, если определит, что может генерировать лучший код без сохранения значение, и оно может также пропустить усечение. Имейте в виду, что цель состоит в том, чтобы как можно быстрее выполнять вычисления с плавающей запятой, а необходимость разбираться с правилами о снижении точности для промежуточных значений просто замедляет процесс.

В большинстве случаев серьезное применение с плавающей запятой - это то, что нужно, чтобы пропустить промежуточное усечение. Правило, требующее усечения при хранении, является скорее надеждой, чем реалистичным требованием.

В дополнение к этому, Java изначально требовала, чтобы все математические операции с плавающей запятой выполнялись с точной точностью, требуемой участвующими типами. Вы можете сделать это на оборудовании Intel, сказав, что не следует расширять типы fp до 80 бит. Это было встречено с громкими жалобами от числа сокращателей, потому что это делает вычисления намного медленнее. Вскоре Java перешла к понятию «строгий» fp и «нестрогий» fp, а при серьезном сокращении чисел используется нестрогий, т. Е. Делает его настолько быстрым, насколько это поддерживает оборудование. Люди, которые хорошо понимают математику с плавающей точкой (к которой относится , а не , включают меня), хотят скорости и знают, как справиться с разницей в точности, которая получается.

0 голосов
/ 02 ноября 2018

static_cast<float>(x) требуется для устранения любой избыточной точности, получая float. Хотя стандарт C ++ обычно позволяет реализациям сохранять избыточную точность с плавающей точкой в ​​выражениях, эта точность должна быть удалена с помощью операторов приведения и присваивания.

Лицензия на использование большей точности содержится в проекте C ++ N4659, пункт 8, пункт 13:

Значения плавающих операндов и результаты плавающих выражений могут быть представлены в большем точность и дальность, чем требуется типом; типы не меняются. 64

Сноска 64 гласит:

Операторы приведения и присваивания все еще должны выполнять свои конкретные преобразования, как описано в 8.4, 8.2.9 и 8.18.

0 голосов
/ 02 ноября 2018

Некоторые компиляторы имеют такую ​​концепцию «расширенной точности», когда двойники несут с собой более 64 бит данных. Это приводит к вычислениям с плавающей запятой, которые не соответствуют стандарту IEEE.

Приведенный выше код может быть попыткой не допустить, чтобы флаги расширенной точности на компиляторе убрали потерю точности. Такие флаги явно нарушают допущения точности значений типа double и значений с плавающей запятой. Кажется вероятным, что они не сделали бы это с volatile переменной.

...