Для получения триального ключа
заполните форму ниже
Team license
Enterprise license
** Нажимая на кнопку, вы даете согласие на обработку
своих персональных данных. См. Политику конфиденциальности

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

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

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

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

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

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


Если вы так и не получили ответ, пожалуйста, проверьте папку
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-битным компилятором в любом случае.

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

Последние статьи:

Опрос:

Популярные статьи по теме
Есть ли жизнь без RTTI: пишем свой dynamic_cast

Дата: 13 Окт 2022

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

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

Дата: 12 Авг 2022

Автор: Михаил Гельвих

Как говорится, в любом деле самое сложное — это начать. Так и мы, очень долго откладывали разделение диагностики V512, но время пришло. Ну а о причинах и последствиях этого решения можно прочитать в …
Почему в С++ массивы нужно удалять через delete[]

Дата: 27 Июл 2022

Автор: Михаил Гельвих

Заметка рассчитана на начинающих C++ программистов, которым стало интересно, почему везде твердят, что нужно использовать delete[] для массивов, но вместо внятного объяснения – просто прикрываются ма…
Межмодульный анализ C и C++ проектов в деталях. Часть 2

Дата: 14 Июл 2022

Автор: Олег Лысый

В первой части статьи мы рассматривали основы теории компиляции C и C++ проектов, в частности особое внимание уделили алгоритмам компоновки и оптимизациям. Во второй части мы погрузимся глубже и пока…
Межмодульный анализ C и C++ проектов в деталях. Часть 1

Дата: 08 Июл 2022

Автор: Олег Лысый

Начиная с PVS-Studio 7.14, для C и C++ анализатора появилась поддержка межмодульного анализа. В этой статье, которая будет состоять из двух частей, мы расскажем, как устроены похожие механизмы в комп…

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

Следующие комментарии
Unicorn with delicious cookie
Мы используем куки, чтобы пользоваться сайтом было удобно.
Хорошо