Подтвердить что ты не робот

Является ли "(float) integer == integer" гарантировано равным в С#?

Хотя "все мы знаем", что x == y может быть проблематичным, где x и y являются значениями с плавающей запятой, этот вопрос немного конкретнее:

int x = random.Next(SOME_UPPER_LIMIT);
float r = x;
// Is the following ALWAYS true?    
r == x

Теперь, поскольку диапазон float of намного больше, чем диапазон целых чисел (но точность недостаточна для однозначного представления целых чисел по краям), было бы неплохо, если бы ответы на этот вопрос также обсуждались, какие значения x это может быть гарантировано, если оно может быть гарантировано вообще.


В настоящее время мой код делает это предположение (для относительно небольших значений x) - я хотел бы убедиться, что я не буду укушен:)


Это приведет к ошибке с "не равным: 16777217" (cast float → int):

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if ((int)f != i) throw new Exception("not equal " + i);
}

Этот аналогичный код не сбой (только int → float); однако из-за потери в преобразовании имеется несколько поплавков, которые могут "равняться" одному и тому же целому числу и могут представлять собой тихую ошибку:

for (int i = 0; i < int.MaxValue; i++) {
   float f = i;
   if (f != i) throw new Exception("not equal " + i);
}
4b9b3361

Ответ 1

Да, сравнение всегда будет истинным, независимо от значения int .

int будет преобразован в float для преобразования, и первое преобразование в float всегда даст тот же результат, что и второе преобразование.

Рассмотрим:

int x = [any integer value];
float y = x;
float z = x;

Значения y и z всегда будут одинаковыми. Если преобразование теряет точность, оба преобразования потеряют точность точно так же.

Если вы преобразуете float обратно в int для сравнения, это другое дело.


Обратите внимание, что даже если конкретное значение int , преобразованное в float, всегда приводит к тому же значению float, это не означает, что значение float должно быть уникальным для этого int стоимость. Существуют значения int , где (float)x == (float)(x+1) будет true.

Ответ 2

Следующий эксперимент показывает, что ответ заключается в том, что у вас нет такого случая, когда равенство не истинно.

    static void Main(string[] args)
    {
        Parallel.For(int.MinValue, int.MaxValue, (x) =>
        {
            float r = x;
            // Is the following ALWAYS true?    
            bool equal = r == x;
            if (!equal) Console.WriteLine("Unequal: " + x);                
        });

        Console.WriteLine("Done");
        Console.ReadKey();

        return;
}

Кажется разумным, что преобразования

float f = i;

и

if ((int)f != i)

должны следовать тем же правилам. Это доказывает, что преобразования int → float и float → int являются биекцией.

ПРИМЕЧАНИЕ: код эксперимента на самом деле не проверяет кросс файл int.MaxValue, поскольку параметр Parallel.For для параметра является исключительным, но я тестировал это значение отдельно, и он также прошел тест.

Ответ 3

При сравнении int и float int неявно передается в float. Это обеспечивает такую ​​же потерю точности, и поэтому сравнение всегда будет истинным. До тех пор, пока вы не нарушите неявное литье или сделать арифметику, должно быть соблюдено равенство. Например, если вы пишете это:

bool AlwaysTrue(int i) {
    return i == (float)i;
}

существует неявный перевод, поэтому он эквивалентен этой функции, которая всегда должна возвращать true:

bool AlwaysTrue(int i) {
    return (float)i == (float)i;
}

но если вы напишете это:

bool SometimesTrue(int i) {
    return i == (int)(float)i;
}

тогда больше нет неявного броска, и потеря точности происходит только с правой стороны. Результат может быть ложным. Аналогично, если вы пишете это:

bool SometimesTrue(int i) {
    return 1 + i == 1 + (float)i;
}

то потеря точности может быть не эквивалентной с обеих сторон. Результат может быть неверным.

Ответ 4

Я запустил этот код без исключения:

for (int x = Int16.MinValue; x < Int16.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

Все еще ждущий (не завершил этот тест, потому что он занимал слишком много времени, никогда не выдавал исключение, хотя во время работы):

for (long x = Int64.MinValue; x < Int64.MaxValue; x++)
{
 float r = x;
 if (r != x)
 {
  throw new Exception("Failed at: " + x);
 }
}

Вернулся и провел ваш пример с оговоркой, это был результат:

[Exception: not equal 16777217 ?= 1.677722E+07 ?= 16777216]

for (int i = 0; i < int.MaxValue; i++)
{
 float f = i;
 if ((int)f != i) throw new Exception("not equal " + i + " ?= " + f + " ?= " + (int)f);
}

Ответ 5

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

Я думал, что архитектура x86, например, гарантировала минимум, но я оказался ошибочным.