metrica
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо
to the top
close form

Заполните форму в два простых шага ниже:

Ваши контактные данные:

Шаг 1
Поздравляем! У вас есть промокод!

Тип желаемой лицензии:

Шаг 2
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности
close form
Запросите информацию о ценах
Новая лицензия
Продление лицензии
--Выберите валюту--
USD
EUR
RUB
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Бесплатная лицензия PVS‑Studio для специалистов Microsoft MVP
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Для получения лицензии для вашего открытого
проекта заполните, пожалуйста, эту форму
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
Мне интересно попробовать плагин на:
* Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

close form
check circle
Ваше сообщение отправлено.

Мы ответим вам на


Если вы так и не получили ответ, пожалуйста, проверьте папку
Spam/Junk и нажмите на письме кнопку "Не спам".
Так Вы не пропустите ответы от нашей команды.

>
>
>
64-битные программы и вычисления с плав…

64-битные программы и вычисления с плавающей точкой

18 Авг 2010

К нам в поддержку обратился разработчик, занимающийся портированием своего Windows-приложения на 64-битную платформу. Он задал вопрос, связанный с использованием вычислений с плавающей точкой. С его разрешения мы публикуем в блоге ответ на вопрос, поскольку эта тема может быть интересна и другим разработчикам.

Текст письма

Хочу задать вам один конкретный вопрос, касающийся миграции 32 -> 64 бита. Статьи и материалы на вашем сайте я изучал, тем более был удивлён тому несоответствию в работе 32- и 64-битного кода, что я обнаружил.

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

float fConst = 1.4318620f; 
float fValue1 = 40.598053f * (1.f - 1.4318620f / 100.f); 
float fValue2 = 40.598053f * (1.f - fConst / 100.f);

MSVC 32, SSE и SSE2 отключены

/fp:precise: fValue1 = 40.016743, fValue2 = 40.016747

MSVC 64, SSE и SSE2 отключены

/fp:precise: fValue1 = 40.016743, fValue2 = 40.016743

Проблема в том, что отличаются значения fValue2. Из-за этого несоответствия код, скомпилированный под 32 и под 64 бита, даёт разные результаты, что недопустимо в моём случае (да и, наверное, недопустимо вообще).

Находит ли что-то похожее ваш продукт? Не могли бы вы дать наводку, как 32/64 может влиять на то, что выдаёт вещественная арифметика?

Наш ответ

Продукт Viva64 не выявляет подобные изменения в поведении в программы после ее перекомпиляции для 64-битной системы. Подобные изменения нельзя назвать ошибочными. Давайте разберемся с описанной ситуацией подробней.

Простое объяснение

Взглянем для начала на то, что выдает 32-битный компилятор: fValue1 = 40.016743, fValue2 = 40.016747.

Вспомним, что тип float имеет 7 значащих цифр. Отсюда видно, что на самом деле мы получаем значение, которое чуть больше 40.01674 (семь значащих цифр). Будет ли это на самом деле 40.016743 или 40.016747 не имеет значения, поскольку это за пределами точности типа float.

При компиляции в 64-битном режиме компилятор генерирует такой же корректный код, в результате которого мы получаем то же самое значение "чуть больше 40.01674". В данном случае это всегда 40.016743. Но это не имеет значения. В рамках точности типа float мы получаем такой же результат, как и в 32-битной программе.

Еще раз - результат вычислений на 32-битной и 64-битной системе одинаков в рамках возможностей типа float.

Более строгое объяснение

Точностью типа float является значение FLT_EPSILON, равное 0.0000001192092896.

Если мы прибавим к 1.0f значение меньше чем FLT_EPSILON, то получим вновь 1.0f. Только прибавление к 1.0f значения равного или большего FLT_EPSILON увеличит значение переменной: 1.0f + FLT_EPSILON !=1.0f.

В нашем случае мы работаем не с единицей, а со значениями 40.016743, 40.016747. Возьмем максимальное из них и умножим на FLT_EPSILON. Полученное число будет значением точности для наших вычислений:

Epsilon = 40.016743*FLT_EPSILON = 40.016743*0.0000001192092896 = 0,0000047703675051357728

Посмотрим, насколько различаются числа 40.016747 и 40.016743:

Delta = 40.016747 - 40.016743 = 0.000004

Оказывается, что разница меньше, чем погрешность:

Delta < Epsilon

0.000004 < 0,00000477

Следовательно, 40.016743 == 40.016747 в рамках типа float.

Как поступить?

Хотя все корректно, от этого, к сожалению часто не легче. Если есть желание сделать систему более детерминированной, то можно использовать ключ /fp:strict.

В этом случае результат работы будет следующий:

MSVC x86:

/fp:strict: fValue1 = 40.016747, fValue2 = 40.016747

MSVC x86-64:

/fp:strict: fValue1 = 40.016743, fValue2 = 40.016743

Результат стал более стабильный, но мы опять не достигли идентичного поведения 32-битного и 64-битного кода. Что делать? Только смириться и изменить методику сравнения результатов.

Не знаю, насколько то, что я опишу, совпадает с вашей ситуацией, но мне кажется это что-то близкое.

Я занимался разработкой пакета численного моделирования. Была поставлена задача, разработать систему регрессионных тестов. Есть набор проектов, результат которых просмотрен физиками и оценен как корректный. Правки кода, вносимые в проект не должны приводить к тому, чтобы выходные данные начали отличаться. Если в какой-то точке в момент t давление 5 атмосфер, то это давление должно остаться в ней и после добавления новой кнопки в диалоге или оптимизации механизма начального заполнения области. Если что-то меняется, то значит, были правки в модели и физики должны заново оценить все изменения. Естественно предполагается, что подобные правки модели крайне редкая ситуация. В нормальном режиме разработки проекта должны получаться идентичные данные. Однако это теоретически. На практике все сложнее. Идентичный результат не всегда можно было получить, работая даже с одним компилятором с одинаковыми ключами оптимизации. Результаты все равно очень легко начинали "плыть". Но поскольку проект еще и собирался различными компиляторами под различные платформы, то получить совершенно идентичные значения было признано не решаемой задачей. Вернее возможно задача и решаемая, но это требует огромное количество усилий и приведет к недопустимому падению скорости вычислений из-за невозможности оптимизаций кода. Решением стала специальная система сравнения результатов. Причем значения в различных точках сравнивались не просто с точностью Epsilon, а специальным образом. Подробности реализации я уже не помню, но идея была следующая. Если в области протекают процессы, в результате которой максимум давления составляет 10 атмосфер, то в другой точке, разница в 0.001 атмосферу считается ошибкой. Однако если протекает процесс, где образуются участки с давлением 1000 атмосфер, то разница в 0.001 уже считается допустимой погрешностью. Таким образом, удалось построить достаточно надежную систему регрессионного тестирования, которая, думается, с успехом работает и поныне.

Последний момент, а почему все-таки мы получаем разный результат в 32-битном и 64-битном коде?

Видимо дело в том, что используется разный набор инструкций. В 64-битном режиме теперь всегда используются SSE2 инструкции, которые реализованы во всех процессорах семейства AMD64 (Intel 64). Кстати, поэтому в исходном вопросе фраза "MSVC 64, SSE и SSE2 отключены" является неверной. SSE2 используется 64-битным компилятором в любом случае.

Дополнительные ресурсы

Популярные статьи по теме
Распространённые паттерны опечаток при программировании

Дата: 25 Авг 2023

Автор: Андрей Карпов

Есть бесконечное количество способов ошибиться при написании кода. Однако иногда можно заметить явные интересные закономерности, как и где ошибаются программисты. Поговорим о коде, который...
60 антипаттернов для С++ программиста

Дата: 30 Май 2023

Автор: Андрей Карпов

Здесь вы найдёте 60 вредных советов для программистов и пояснение, почему они вредные. Всё будет одновременно в шутку и серьёзно. Как бы глупо ни смотрелся вредный совет, он не выдуман, а подсмотрен.…
Тонкости C++: итак, вы объявили класс…

Дата: 07 Фев 2023

Автор: Сергей Ларин

Во время работы наша команда постоянно сталкивается с некоторыми особенностями языка, которые могут быть неизвестны рядовому C++ программисту. В этой статье мы расскажем о том, как работает, казалось…
Под капотом SAST: как инструменты анализа кода ищут дефекты безопасности

Дата: 26 Янв 2023

Автор: Сергей Васильев

Сегодня речь о том, как SAST-решения ищут дефекты безопасности. Расскажу, как разные подходы к поиску потенциальных уязвимостей дополняют друг друга, зачем нужен каждый из них и как теория ложится на…
Есть ли жизнь без RTTI: пишем свой dynamic_cast

Дата: 13 Окт 2022

Автор: Владислав Столяров

В современном С++ осталось не так много вещей, которые не подходят под парадигму "Не плати за то, что не используешь". Одна из них – dynamic_cast. В рамках данной статьи мы разберёмся, что с ним не...


Комментарии (0)

Следующие комментарии next comments
close comment form