Я испытываю очень странную проблему с нашим тестовым кодом в коммерческом продукте под 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/... (портировано, чтобы плавать, конечно)