Ответ 1
Комментарий Дэвида правильный, но недостаточно сильный. Нет никакой гарантии, что выполнение этого вычисления дважды в одной и той же программе даст одинаковые результаты.
Спецификация С# предельно ясна в этом пункте:
Операции с плавающей запятой могут выполняться с большей точностью, чем тип результата операции. Например, некоторые аппаратные архитектуры поддерживают "расширенный" или "длинный двойной" тип с плавающей запятой с большим диапазоном и точностью, чем двойной тип, и неявно выполняют все операции с плавающей запятой, используя этот более высокий тип точности. Только при чрезмерной стоимости производительности могут быть созданы такие аппаратные архитектуры для выполнения операций с плавающей запятой с меньшей точностью и вместо того, чтобы требовать, чтобы реализация лишилась производительности и точности, С# позволяет использовать более высокий тип точности для всех операций с плавающей запятой, Помимо предоставления более точных результатов, это редко имеет какие-либо измеримые эффекты. Однако в выражениях формы
x * y / z
, где умножение дает результат, выходящий за пределы двойного диапазона, но последующее деление возвращает временный результат обратно в двойной диапазон, тот факт, что выражение оценивается в формате более высокого диапазона может привести к получению конечного результата вместо бесконечности.
Компилятор С#, джиттер и среда выполнения имеют широкую широту, чтобы дать вам более точные результаты, чем это требуется в спецификации, в любое время по прихоти - им не требуется выбирать, чтобы делать это последовательно и в факт, что они этого не делают.
Если вам это не нравится, не используйте двоичные числа с плавающей запятой; либо используйте десятичные знаки или произвольные прецизионные рациональности.
Я не понимаю, почему кастинг для float в методе, который возвращает float, делает разницу в нем
Отличная точка.
Ваша примерная программа показывает, как небольшие изменения могут вызвать большие эффекты. Вы заметили, что в некоторой версии среды выполнения приведение в float явно дает другой результат, чем не делает этого. Когда вы явно используете float, компилятор С# дает подсказку для среды выполнения, чтобы сказать: "Извлеките эту вещь из режима сверхвысокой точности, если вы используете эту оптимизацию". Как отмечается в спецификации, это потенциальная стоимость исполнения.
То, что это происходит, округляется до "правильного ответа", это просто счастливая случайность; правильный ответ получается потому, что в этом случае потеря точности была потеряна в правильном направлении.
Как отличается .net 4?
Вы спрашиваете, в чем разница между 3.5 и 4.0 runtimes; разница в том, что в 4.0, дрожание решает перейти к более высокой точности в вашем конкретном случае, а 3,5-джиттер не хочет этого делать. Это не означает, что эта ситуация невозможна в 3.5; это было возможно в каждой версии среды выполнения и каждой версии компилятора С#. Вы только что столкнулись с ситуацией, когда на вашей машине они отличаются деталями. Но джиттеру всегда позволяли делать эту оптимизацию, и всегда делал это по своей прихоти.
Компилятор С# также полностью в пределах своих прав, чтобы выбрать аналогичные оптимизации при вычислении постоянных поплавков во время компиляции. Два, казалось бы, идентичные вычисления в константах могут иметь разные результаты в зависимости от деталей состояния выполнения компилятора.
В более общем плане, ваше ожидание того, что числа с плавающей запятой должны иметь алгебраические свойства действительных чисел, полностью не соответствует действительности; они не обладают этими алгебраическими свойствами. Операции с плавающей точкой даже не являются ассоциативными; они, конечно, не подчиняются законам мультипликативных инверсий, как вы, кажется, ожидаете их. Числа с плавающей запятой являются лишь приближением действительной арифметики; приближение, которое достаточно близко, например, для моделирования физической системы или вычисления сводных статистических данных или некоторой такой вещи.