Страницы

Поиск по вопросам

понедельник, 30 декабря 2019 г.

Множественные ошибки вычислений с плавающей точкой

#cpp #c #double


Есть функция 

inline unsigned long long d(double d)
{
    return (864E9 * d + 0x014f35a9a90cc000 - 0x019DB1DED53E8000) / 10;
}


При ее работе вылетает такая ошибка(код 0xC00002B4)


  Множественные ошибки вычислений с плавающей точкой (parameters: 0x00000000, 0x000005A0).


Самое интересное что эта функция вызывается дважды с одним и тем же входным параметром.
И такая ошибка появляется при повторном вызове. Я пробовал сделать семпл в отдельном
проекте но воспроизвести ошибку не получилось.

Что эта ошибка вообще означает? и как это можно исправить?

PS. VisualStudio 2015

Настройки проекта(Code Generation)

Smaller Type Check: No
Basic Runtime Checks: Default
Runtime Library: /MDd
Floating Point Model: Precise (/fp:precise)


PS2. Если переписать функцию так:

inline unsigned long long GetTsFromDate(double date)
{
    unsigned long long tmp = 0x014f35a9a90cc000 - 0x019DB1DED53E8000;
    double dbl = 864E9 * date;
    double dbl2 = dbl + tmp;
    double result = dbl2 / 10;
    return result;
}


То будет падать на double dbl = 864E9 * date
    


Ответы

Ответ 1



Провёл несколько экспериментов в Visual Studio 2010. Создал консольное приложение с настройками по умолчанию (в частности, модель вычислений с плавающей точкой /fp:precise, исключения с плавающей точкой отключены /fp:except-). Все примеры выполнялись в Debug-режиме. Воспользуемся функцией _statusfp2 для того чтобы узнать floating-point status word в следующем простом примере: #include #include #include using namespace std; #pragma fenv_access (on) int main() { cout.precision(numeric_limits::max_digits10); unsigned int x86, SSE2; double d; _statusfp2(&x86, &SSE2); cout << x86 << " " << SSE2 << endl; d = 0.0; d = d * 1.0; _statusfp2(&x86, &SSE2); cout << d << endl; cout << x86 << " " << SSE2 << endl; return 0; } В моём случае, программа выдала следующий результат: 0 0 0 0 0 Насколько я понял, нулевые значения переменных x86 и SSE2 означают, что при вычислениях с плавающей точкой никаких исключительных ситуаций не произошло. Это логично, ибо никаких "опасных" вычислений в данном пример не осуществляется. Воспользуемся примером из вашего вопроса: d = 864E9; d = d * 43083.341116850243; _statusfp2(&x86, &SSE2); cout << d << endl; cout << x86 << " " << SSE2 << endl; В моем случае программа выдала следующий результат: 37224006724958608 1 0 В приведённом выводе программы есть два важных момента. Во-первых, результат перемножения двух чисел 864E9 и 43083.341116850243 получился не точным. Это легко проверить перемножением этих чисел на калькуляторе: точный результат содержит 20 значащих цифр. Такое число не может быть представлено в переменной типа double. Во-вторых, изменилось состояние floating-point status word. Судя по справке, status word приняло значение _EM_INEXACT. То есть имела место исключительная ситуация — результат арифметической операции не может быть точно представлен в переменной типа double. Тем не менее, программа завершила свою работу нормально, никакого исключения сгенерировано не было. Это обусловлено тем, что по-умолчанию исключения с плавающей точкой замаскированы. Воспользуемся функцией _controlfp_s для того, чтобы демаскировать исключение _EM_INEXACT: unsigned int currentControl _clearfp(); _controlfp_s(¤tControl, 0, 0); _controlfp_s(nullptr, currentControl & ~static_cast(_EM_INEXACT), _MCW_EM); d = 864E9; d = d * 43083.341116850243; В этот раз на строке d = d * 43083.341116850243; выскакивает ошибка: 0xC000008F: Floating-point inexact result. В настройках проекта включим расширенный набор инструкций /arch:SSE2 и на той же самой строке выскакивает немного другая ошибка: 0xC00002B4: Множественные ошибки вычислений с плавающей точкой. А это весьма похоже на ошибку в вашем вопросе. Полагаю, где-то в вашем проекте по какой-то причине демаскируются исключения с плавающей точкой, и обратная маскировка не производится. Помимо исключения _EM_INEXACT есть также исключения _EM_INVALID, _EM_DENORMAL, _EM_ZERODIVIDE, _EM_OVERFLOW и _EM_UNDERFLOW. Замаскировать их все можно следующим образом: _clearfp(); _controlfp_s(nullptr, _MCW_EM, _MCW_EM); P.S. При манипулировании floating-point status word прагма #pragma fenv_access (on) обязательна, иначе компилятор может проигнорировать манипуляции. Также, если всё-таки хочется обрабатывать исключения с плавающей точкой, то судя по всему необходимо использовать опцию /fp:except, иначе может что-нибудь сломаться. Но тут я не уверен.

Ответ 2



В вашей второй функции первая строка меня очень смущает. unsigned long long tmp = 0x014f35a9a90cc000 - 0x019DB1DED53E8000; Зачем вы присваиваете unsigned long число, которое заведомо меньше нуля? Тут даже видно, 0х014 - 0х019. И зачем вам long long, когда хватит обычного long? У меня при выполнении этого действия(отладка) tmp == 18424652457709551616 Далее, смотрю на первую функцию. Вижу что-то вроде переполнения. 864E9 * d + 0x014f35a9a90cc000 здесь вы к double результату 864E9 * d прибавляете 0x014f35a9a90cc000, а второе число уже длиннее пятнадцати значащих знаков double, вряд ли вы контролируете это переполнение. (у double после 15-го знака идут нули) Ну и далее, от сомнительного результата предыдущего действия вы отнимаете еще один long, значение которого опять же больше double. Вот вам и множественные ошибки вычислений. По умолчанию такие ошибки(исключения) скрыты, у вас, видимо, они включены.

Комментариев нет:

Отправить комментарий