Подтвердить что ты не робот

Может ли что-то в С# изменить поведение сравнения float во время выполнения? [64]

Я испытываю очень странную проблему с нашим тестовым кодом в коммерческом продукте под 64-разрядной версией Windows 7 с VS 2012.net 4.5, скомпилированным как 64 бит.

Следующий тестовый код, выполняемый в отдельном проекте, ведет себя как и ожидалось (с помощью NUnit test runner):

[Test]
public void Test()
{
    float x = 0.0f;
    float y = 0.0f;
    float z = 0.0f;

    if ((x * x + y * y + z * z) < (float.Epsilon))
    {
        return;
    }
    throw new Exception("This is totally bad");
}

Тест возвращается как сравнение с < float.Epsilon всегда верен для x, y и z равным 0.0f.

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

В коммерческом продукте этот тестовый пример завершается с ошибкой, когда мой тестовый пример выполняет дополнительный код настройки (предназначен для интеграционных тестов), где инициализируется очень большая система (С#, CLI и очень большая часть С++). Я не могу копать дальше в этом настроенном вызове, поскольку он практически загружает все.

Я ничего не знаю о С#, который мог бы повлиять на оценку.

Бонусная странность: Когда я сравниваю с меньшим или равным с float.Epsilon:

if ((x * x + y * y + z * z) <= (float.Epsilon)) // this works!

то тест будет успешным. Я пробовал сравнивать только с less-than и float.Epsilon * 10, но это не сработало:

if ((x * x + y * y + z * z) < (float.Epsilon*10)) // this doesn't!

Я безуспешно искал эту проблему и даже несмотря на сообщения Эрика Липперта и др. как правило, идут к float.Epsilon. Я не совсем понимаю, какой эффект применяется к моему коду. Является ли это некоторой настройкой С#, влияет ли массивная натура и управляет системой. Что-то в CLI?

Изменить: Еще несколько вещей, чтобы обнаружить: Я использовал GetComponentParts с этой страницы MSDN http://msdn.microsoft.com/en-us/library/system.single.epsilon%28v=vs.110%29.aspx, чтобы визуализировать мои показатели конца мантиассы, и вот результаты:

Тестовый код:

 float x = 0.0f;
 float y = 0.0f;
 float z = 0.0f;
 var res = (x*x + y*y + z*z);
 Console.WriteLine(GetComponentParts(res));
 Console.WriteLine();
 Console.WriteLine(GetComponentParts(float.Epsilon));

Без вся цепочка форварда, которую я получаю (тестовые проходы)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

1.401298E-45: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

С помощью полной загрузочной цепи я получаю (тест не работает)

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000000

Замечания: Поплавок .Epsilon потерял свой последний бит в своей мантиссе.

Я не вижу, как флаг компилятора /fp в С++ влияет на представление float.Epsilon.


Изменить и окончательный вердикт Хотя для получения float.Epsilon можно использовать отдельный поток, он будет вести себя иначе, чем ожидалось, в потоке с сокращенным словом FPU.

В сокращенном потоке слов FPU это вывод "flo-of-thread" с плавающей запятой. Epsilon

0: Sign: 0 (+)
   Exponent: 0xFFFFFF82 (-126)
   Mantissa: 0x0000000000001

Обратите внимание, что последний бит мантиссы равен 1, как и ожидалось, но это значение float все равно будет интерпретироваться как 0. Это, конечно, имеет смысл, поскольку мы используем точность float, которая больше, чем набор слов FPU, но может быть ловушка для кого-то.

Я решил перейти к машине fps, которая вычисляется один раз, как описано здесь: qaru.site/info/210418/... (портировано, чтобы плавать, конечно)

4b9b3361

Ответ 1

Известно, что DirectX модифицирует настройки FPU. См. Этот связанный вопрос: Может ли точность с плавающей запятой быть зависимой от потока?

Вы можете указать DirectX для сохранения настроек FPU, указав флаг D3DCREATE_FPU_PRESERVE при вызове CreateDevice или выполнить свой код с плавающей точкой в ​​новом потоке.

Ответ 2

Если вы получаете разницу между тем, когда вы отлаживаете, и когда он запускается в режиме деблокирования, вы можете не заметить следующее:

(Из MS Partition I, 12.1.3):

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

и

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

и окончательное примечание:

[Примечание. Использование внутреннего представления, которое больше, чем float32 или float64, может привести к различиям в вычислительные результаты, когда разработчик делает, по-видимому, несвязанные модификации своего кода, результат который может быть таким, что значение выводится из внутреннего представления (например, в регистре) в местоположение на стек. end note]

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

Ответ 3

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

  • Если вы запускаете этот тест в начале вашего приложения (например, в основной/вводной процедуре), он не работает?
  • Если это верно, запустите новый проект, используя ту же целевую структуру и архитектуру, которые выполняют один и тот же тест. Если он пройдет, начните постепенно добавлять биты вашего основного приложения и посмотрите, можете ли вы найти, какой бит заставляет его потерпеть неудачу.