Режим выпуска использует двойную точность даже для переменных с плавающей точкой - PullRequest
3 голосов
/ 08 июня 2011

Мой алгоритм вычисляет эпсилон для одинарной точности арифметики с плавающей запятой.Это должно быть что-то около 1.1921e-007.Вот код:

static void Main(string[] args) {
    // start with some small magic number
    float a = 0.000000000000000013877787807814457f;
    for (; ; ) {
        // add the small a to 1
        float temp = 1f + a;
        // break, if a + 1 really is > '1'
        if (temp - 1f != 0f) break;
        // otherwise a is too small -> increase it
        a *= 2f;
        Console.Out.WriteLine("current increment: " + a); 
    }
    Console.Out.WriteLine("Found epsilon: " + a); 
    Console.ReadKey(); 
}

В режиме отладки он дает следующий разумный вывод (сокращенно):

current increment: 2,775558E-17
current increment: 5,551115E-17
...
current increment: 2,980232E-08
current increment: 5,960464E-08
current increment: 1,192093E-07
Found epsilon: 1,192093E-07

Однако при переходе в режим разблокировки (независимо от того, с / безоптимизация!), код дает следующий результат:

current increment: 2,775558E-17
current increment: 5,551115E-17
current increment: 1,110223E-16
current increment: 2,220446E-16
Found epsilon: 2,220446E-16

, что соответствует значению для двойной точности .Поэтому я предполагаю, что некоторые оптимизации приводят к тому, что вычисления выполняются с двойными значениями.Конечно, результат неправильный в этом случае!

Также: это происходит только при нацеливании X86 Release в опциях проекта.Опять же: оптимизация вкл / выкл не имеет значения.Я использую 64-разрядную версию WIN7 VS 2010 Ultimate, ориентированную на .NET 4.0.

Что может вызвать такое поведение?Какая-то ВАУ проблема?Как обойти это надежным способом?Как запретить CLR генерировать код, который использует двойную точность вместо вычислений с одинарной точностью?

Примечание: переключение на «Любой ЦП» или даже «X64» в качестве целевой платформы не вариант - даже если проблема здесь не возникает.Но у нас есть некоторые нативные библиотеки, в разных версиях для 32/64 бит.Таким образом, цель должна быть конкретной.

1 Ответ

3 голосов
/ 08 июня 2011

Как обсуждалось в комментариях, это ожидается. Его можно обойти, удалив способность JIT сохранять значение в регистре (который будет шире, чем фактическое значение), - принудительно опустив его в поле (которое имеет четко определенный размер):

class WorkingContext
{ 
    public float Value; // you'll excuse me a public field here, I trust
    public override string ToString()
    {
        return Value.ToString();
    }
}
static void Main()
{
    // start with some small magic number
    WorkingContext a = new WorkingContext(), temp = new WorkingContext();
    a.Value = 0.000000000000000013877787807814457f;
    for (; ; )
    {
        // add the small a to 1
        temp.Value = 1f + a.Value;
        // break, if a + 1 really is > '1'
        if (temp.Value - 1f != 0f) break;
        // otherwise a is too small -> increase it
        a.Value *= 2f;
        Console.Out.WriteLine("current increment: " + a);
    }
    Console.Out.WriteLine("Found epsilon: " + a);
    Console.ReadKey();
}

Интересно, что сначала я попробовал это с помощью структуры, но JIT смог увидеть прошлое моего мошенничества (предположительно, потому что это все в стеке).

...