Как C # оценивает число с плавающей запятой при наведении и промежуточном окне по сравнению со скомпилированным? - PullRequest
7 голосов
/ 10 сентября 2009

Я вижу что-то странное с сохранением двойников в словаре, и я не совсем понимаю, почему.

Вот код:

            Dictionary<string, double> a = new Dictionary<string, double>();
            a.Add("a", 1e-3);

            if (1.0 < a["a"] * 1e3)
                Console.WriteLine("Wrong");

            if (1.0 < 1e-3 * 1e3)
                Console.WriteLine("Wrong");

Второй оператор if работает как положено; 1,0 не менее 1,0. Теперь первый оператор if оценивается как true. Очень странно то, что когда я наводю курсор мыши на if, intellisense сообщает мне false, но код успешно перемещается в Console.WriteLine.

Это для C # 3.5 в Visual Studio 2008.

Это проблема точности с плавающей запятой? Тогда почему второй оператор if работает? Я чувствую, что упускаю что-то очень фундаментальное здесь.

Любое понимание приветствуется.

Edit2 (Немного переформулировав вопрос):

Я могу принять математическую проблему точности, но мой вопрос сейчас таков: почему наведение правильно оценивается? Это также верно для промежуточного окна. Я вставляю код из первого оператора if в промежуточное окно, и оно оценивает false.

Обновление

Прежде всего, большое спасибо за все великолепные ответы.

У меня также возникают проблемы при воссоздании этого в другом проекте на той же машине. Глядя на настройки проекта, я не вижу различий. Глядя на IL между проектами, я не вижу различий. Глядя на разборки, я не вижу видимых отличий (кроме адресов памяти). Тем не менее, когда я отлаживаю оригинальный проект, я вижу: screenshot of problem

В ближайшем окне сообщается, что if ложно, но код попадает в условное выражение.

В любом случае, я думаю, что лучший ответ - подготовиться к арифметике с плавающей запятой в этих ситуациях. Причина, по которой я не мог этого допустить, больше связана с вычислениями отладчика, отличающимися от времени выполнения. Так что большое спасибо Брайану Гидеону и Стефентирону за некоторые очень проницательные комментарии.

Ответы [ 4 ]

13 голосов
/ 10 сентября 2009

Это проблема с плавающей точностью.

Второе утверждение работает, потому что компилятор считает выражение 1e-3 * 1e3 перед выпуском .exe.

Посмотрите на это в ILDasm / Reflector, он выдаст что-то вроде

 if (1.0 < 1.0)
                Console.WriteLine("Wrong");
4 голосов
/ 10 сентября 2009

Проблема здесь довольно тонкая. Компилятор C # (всегда) не генерирует код, который выполняет вычисления в два раза, даже если это тип, который вы указали. В частности, он генерирует код, который выполняет вычисления с «расширенной» точностью, используя инструкции x87, без округления промежуточных результатов до двойного.

В зависимости от того, оценивается ли 1e-3 как двойное или длинное двойное, и умножается ли умножение на двойное или длинное двойное, можно получить любой из следующих трех результатов:

  • (long double) 1e-3 * 1e3, вычисленное в long double, равно 1,0 - epsilon
  • (double) 1e-3 * 1e3, вычисленное в двойном размере, равно точно 1,0
  • (double) 1e-3 * 1e3, вычисленное в long double, равно 1,0 + эпсилон

Ясно, что первое сравнение, которое не соответствует вашим ожиданиям, оценивается так, как описано в третьем сценарии, который я перечислил. 1e-3 округляется до удвоения либо потому, что вы сохраняете и загружаете его снова, что вызывает округление, либо потому, что C # распознает 1e-3 как литерал двойной точности и обрабатывает его таким образом. Умножение вычисляется в long double, потому что C # имеет модель с умопомрачительными цифрами , так компилятор генерирует код.

Умножение во втором сравнении либо оценивается с использованием одного из двух других методов, (Вы можете выяснить, какой, пытаясь "1> 1e-3 * 1e3"), либо компилятор округляет результат умножения перед сравнением его с 1.0, когда оно вычисляет выражение во время компиляции.

Вероятно, вы можете указать компилятору не использовать расширенную точность, не сообщив об этом с помощью какой-либо настройки сборки; может также работать включение codegen для SSE2.

2 голосов
/ 10 сентября 2009

Ммм ... странно. Я не могу воспроизвести вашу проблему. Я использую C # 3.5 и Visual Studio 2008, а также. Я набрал в вашем примере точно , как он был опубликован, и я не вижу ни выполнения оператора Console.WriteLine.

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

Наконец, я все равно не понимаю, как это может быть проблемой точности с плавающей запятой. Почему компилятор C # статически оценивает два значения double по-другому, чем CLR во время выполнения? Если это действительно так, то можно привести аргумент, что в компиляторе C # есть ошибка.

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

2 голосов
/ 10 сентября 2009

Смотрите ответы здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...