С# округляется по-разному в зависимости от платформы?

У меня есть этот крошечный фрагмент кода

double s = -2.6114289999999998;
double s7 = Math.Round(s, 7);
double s5 = Math.Round(s, 5);
double s6 = Math.Round(s, 6);

С платформой = любой процессор, я получаю

s7: -2.611429   
s5: -2.61143    
s6: -2.611429   

С платформой = x64 я получаю

s7: -2.6114289999999998 
s5: -2.61143    
s6: -2.6114289999999998

Почему? (Результат скопирован из окна VS Locals)


Весь фрагмент кода:

    private void btnAlign_Click(object sender, EventArgs e)
    {
        double s = -2.6114289999999998;
        double s7 = Math.Round(s, 7);
        double s5 = Math.Round(s, 5);
        double s6 = Math.Round(s, 6);
    }

Ответ 1

Значение -2.611429 не может быть представлено с использованием 64-битной с плавающей запятой. При компиляции в 32-битном режиме значение вместо этого будет использовать 80-битную (расширенную точность).

Ответ 2

В x64 используется SSU2 FPU, а на x86 используется xpp x90.

Возможно (хотя и не рекомендуется) изменять точность x87 так же, как точность SSE2 (т.е. использовать более низкую точность).

Вы можете сделать это с помощью _controlfp() API.

Следующая программа демонстрирует. Запустите эту программу в режиме x86, и вы увидите, как использование _controlfp(0x00030000, 0x00020000) приводит к тому, что вывод будет схожим с (но не совсем таким же!) Как версия x64:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp3
{
    class Program
    {
        static void Main()
        {
            double s = -2.6114289999999998;

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.611429

            if (!Environment.Is64BitProcess)
                _controlfp(0x00030000, 0x00020000);

            Console.WriteLine(Math.Round(s, 7).ToString("R")); // -2.61142897605896
        }

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern uint _controlfp(uint newcw, uint mask);
    }
}

Однако вам не следует обходиться с FPU таким образом (и если вы это сделаете, вы должны вернуться к предыдущей настройке как можно скорее).

Ответ 3

В соответствии со спецификацией языка CSharp 3.0 может иметь место следующее:

Более подробную информацию см. в главе 4.1.6.

Операции с плавающей запятой могут выполняться с большей точностью, чем тип результата операции. Например, некоторые аппаратные архитектуры поддерживают "расширенный" или "длинный двойной" тип с плавающей запятой с большим диапазоном и точностью, чем двойной тип, и неявно выполняют все операции с плавающей запятой, используя этот более высокий тип точности. Только при чрезмерной стоимости производительности могут быть созданы такие аппаратные архитектуры для выполнения операций с плавающей запятой с меньшей точностью и вместо того, чтобы требовать, чтобы реализация лишилась производительности и точности, С# позволяет использовать более высокий тип точности для всех операций с плавающей запятой, Помимо предоставления более точных результатов, это редко имеет какие-либо измеримые эффекты. Однако в выражениях вида x * y/z, где умножение дает результат, выходящий за пределы двойного диапазона, но последующее деление возвращает временный результат обратно в двойной диапазон, тот факт, что выражение оценивается в более высоком диапазон может привести к тому, что конечный результат будет создан вместо бесконечности.

Вкратце: спецификация С# фактически заявляет, что аппаратные архитектуры могут иметь некоторое влияние на типы с плавающей точкой (Double, Float).

Спецификацию языка как .doc можно найти здесь.

Ответ 4

Отвечено здесь: fooobar.com/info/299800/...

x64 управляемый код будет использовать SSE для вычисления double/float вместо x87 FPU при использовании управляемого кода x86.