C++ обработка избыточной точности
В настоящее время я смотрю на код, который выполняет арифметику с плавающей точкой с множественной точностью. Для корректной работы этого кода требуется, чтобы значения были сведены к конечной точности в четко определенных точках. Таким образом, даже если промежуточный результат был вычислен для 80-разрядного регистра с плавающей запятой с повышенной точностью, в какой-то момент он должен быть округлен до 64-разрядного двойного для последующих операций.
Код использует макрос INEXACT
описать это требование, но не имеет идеального определения. Руководство GCC упоминает -fexcess-precision=standard
как способ заставить четко определенную точность для операций приведения и присваивания. Тем не менее, он также пишет:
'-fexcess-precision = standard' не реализован для языков, отличных от C
Сейчас я думаю о переносе этих идей на C++ (комментарии приветствуются, если кто-нибудь знает существующую реализацию). Так что, похоже, я не могу использовать этот переключатель для C++. Но каково поведение g++ по умолчанию при отсутствии какого-либо переключателя? Существуют ли еще C++-подобные способы управления обработкой избыточной точности?
Я думаю, что для моего текущего случая использования, я, вероятно, буду использовать -mfpmath=sse
в любом случае, что, насколько я знаю, не должно приводить к какой-либо излишней точности. Но мне все еще интересно.
2 ответа
Существуют ли еще C++-подобные способы управления обработкой избыточной точности?
Стандарт C99 определяет FLT_EVAL_METHOD
макрос, установленный компилятором, который определяет, как должна происходить избыточная точность в программе на C (многие компиляторы C по-прежнему ведут себя так, что не совсем соответствуют наиболее разумной интерпретации значения FP_EVAL_METHOD
что они определяют: более старые версии GCC, генерирующие код 387, Clang при генерации кода 387, …). Тонкие моменты в связи с последствиями FLT_EVAL_METHOD
были уточнены в стандарте C11.
Начиная со стандарта 2011 года, C++ использует C99 для определения FLT_EVAL_METHOD
(заголовок cfloat).
Так что GCC должен просто позволить -fexcess-precision=standard
для C++, и, надеюсь, это в конечном итоге будет. Та же семантика, что и в C, уже есть в стандарте C++, их нужно реализовывать только в компиляторах C++.
Я предполагаю, что для моего текущего варианта использования я, вероятно, буду использовать -mfpmath = sse в любом случае, что, насколько я знаю, не должно приводить к какой-либо избыточной точности.
Это обычное решение.
Помните, что C99 также определяет FP_CONTRACT
в math.h, на который вы, возможно, захотите взглянуть: это относится к той же проблеме некоторых выражений, которые вычисляются с более высокой точностью, с совершенно другой стороны (современная инструкция fused-multiply-add вместо старого набора команд 387). Это прагма, чтобы решить, разрешено ли компилятору заменять сложения и умножения на уровне исходного кода инструкциями FMA (это приводит к тому, что умножение фактически вычисляется с бесконечной точностью, потому что именно так работает эта инструкция, а не округляется до точность типа, как это было бы с отдельными инструкциями умножения и сложения). Эта прагма, по-видимому, не была включена в стандарт C++ (насколько я вижу).
Значение по умолчанию для этой опции определяется реализацией, и некоторые люди утверждают, что по умолчанию разрешено генерировать инструкции FMA (для компиляторов C, которые в противном случае определяют FLT_EVAL_METHOD
как 0). В C вы должны рассчитывать на будущее свой код с помощью:
#include <math.h>
#pragma STDC FP_CONTRACT off
И эквивалентное заклинание в C++, если ваш компилятор документирует его.
каково поведение g++ по умолчанию при отсутствии какого-либо переключателя?
Я боюсь, что ответ на этот вопрос заключается в том, что поведение GCC, скажем, при генерации кода 387, является бессмысленным. См. Описание ситуации, которая побудила Джозефа Майерса исправить ситуацию для C. Если g++ не реализует -fexcess-precision=standard
, это, вероятно, означает, что 80-битные вычисления случайным образом округляются до точности типа, когда компилятору пришлось пролить некоторые регистры с плавающей запятой в память, что привело к тому, что программа в некоторых случаях выводила "foo" вне контроля программиста:
if (x == 0.0) return;
... // code that does not modify x
if (x == 0.0) printf("foo\n");
... потому что код в многоточии вызвал x
, который был сохранен в 80-битном регистре с плавающей запятой, чтобы быть разлитым в 64-битный слот в стеке.
Но каково поведение g++ по умолчанию при отсутствии какого-либо переключателя?
Я нашел один ответ сам с помощью эксперимента, используя следующий код:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv) {
double a = atof("1.2345678");
double b = a*a;
printf("%.20e\n", b - 1.52415765279683990130);
return 0;
}
Если b
округляется (-fexcess-precision=standard
), то результат равен нулю. Иначе (-fexcess-precision=fast
) это что-то вроде 8e-17
, Компилирование с -mfpmath=387 -O3
Я мог бы воспроизвести оба случая для gcc-4.8.2
, За g++-4.8.2
Я получаю ошибку за -fexcess-precision=standard
если я попробую это, и без флага я получу то же поведение, что и -fexcess-precision=fast
дает за C. Добавление -std=c++11
не помогает. Так что теперь подозрение, уже высказанное Паскалем, является официальным: g++ не обязательно округляется везде, где следует.