Проверьте, является ли число с плавающей запятой целым - PullRequest
17 голосов
/ 27 сентября 2008

Этот код работает (C # 3)

double d;
if(d == (double)(int)d) ...;
  1. Есть ли лучший способ сделать это?
  2. По посторонним причинам я хочу избежать двойного применения; какие хорошие способы существуют кроме этого? (даже если они не так хороши)

Примечание: Несколько человек указали на (важный) момент, что == часто проблематично пересматривать с плавающей запятой. В этом случае я ожидаю значения в диапазоне от 0 до нескольких сотен, и они должны быть целыми числами (не целые числа - ошибки), поэтому, если эти точки «не должны» представлять для меня проблему.

Ответы [ 12 ]

27 голосов
/ 27 сентября 2008
d == Math.Floor(d)

делает то же самое другими словами.

NB. Надеюсь, вы понимаете, что должны быть очень осторожны, когда делаете подобные вещи; float / double очень легко накапливает незначительные ошибки, которые приводят к сбою точных сравнений (подобных этому) без видимой причины.

5 голосов
/ 27 сентября 2008

Я не могу ответить на специфическую для C # часть вопроса, но должен отметить, что вы, вероятно, упускаете общую проблему с числами с плавающей запятой.

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

Например, 1.1 + 0.6 != 1.7.

Да, именно так работают числа с плавающей запятой.

Здесь 1.1 + 0.6 - 1.7 == 2.2204460492503131e-16.

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

Если этого недостаточно, вы должны работать с представлением десятичного числа, с представлением числа с плавающей запятой со встроенным диапазоном ошибок или с символьными вычислениями.

5 голосов
/ 27 сентября 2008

Это будет работать, я думаю:

if (d % 1 == 0) {
  //...
}
5 голосов
/ 27 сентября 2008

Если ваш двойник является результатом другого вычисления, вы, вероятно, хотите что-то вроде:

d == Math.Floor(d + 0.00001);

Таким образом, если произошла небольшая ошибка округления, она все равно будет совпадать.

3 голосов
/ 27 сентября 2008

Если вы просто собираетесь конвертировать его, ответ Майка Ф. / Хота хорош, но не совсем отвечает на ваш вопрос. Если вы собираетесь на самом деле тестировать, и это действительно важно, я рекомендую вам реализовать что-то, что содержит предел погрешности.

Например, если вы думаете о деньгах и хотите проверить даже суммы в долларах, вы можете сказать (следуя схеме Кота):

if( Math.abs(d - Math.Floor(d + 0.001)) < 0.001)

Другими словами, возьмите абсолютное значение разности значения и его целочисленное представление и убедитесь, что оно мало.

2 голосов
/ 22 февраля 2013

Математически гарантируется, что простой тест, такой как 'x == floor (x)', будет работать правильно для любого числа FP фиксированной точности.

Все допустимые кодировки FP с фиксированной точностью представляют разные действительные числа, и поэтому для каждого целого числа x существует не более одной кодировки FP с фиксированной точностью, которая точно ей соответствует.

Следовательно, для каждого целого числа x, которое МОЖЕТ быть представлено таким образом, мы обязательно имеем x == floor (x), так как floor (x) по определению возвращает наибольшее число FP y, такое что y <= x и y представляет целое число; поэтому floor (x) должен вернуть x. </p>

2 голосов
/ 27 сентября 2008

Использовать Math.Truncate ()

2 голосов
/ 27 сентября 2008

Вам не нужно дополнительное (двойное) там. Это работает:

if (d == (int)d) {
 //...
}
1 голос
/ 27 сентября 2008

Это позволит вам выбрать, какую точность вы ищете, плюс-минус половина тика, чтобы учесть дрейф с плавающей запятой. Сравнение также является неотъемлемым, что приятно.

static void Main(string[] args)
{
    const int precision = 10000;

    foreach (var d in new[] { 2, 2.9, 2.001, 1.999, 1.99999999, 2.00000001 })
    {
        if ((int) (d*precision + .5)%precision == 0)
        {
            Console.WriteLine("{0} is an int", d);
        }
    }
}

и вывод

2 is an int
1.99999999 is an int
2.00000001 is an int
0 голосов
/ 19 ноября 2012

Чтобы справиться с точностью двойного ...

Math.Abs(d - Math.Floor(d)) <= double.Epsilon

Рассмотрим следующий случай, когда значение меньше, чем double.Epsilon не удается сравнить как ноль.

// number of possible rounds
const int rounds = 1;

// precision causes rounding up to double.Epsilon
double d = double.Epsilon*.75;

// due to the rounding this comparison fails
Console.WriteLine(d == Math.Floor(d));

// this comparison succeeds by accounting for the rounding
Console.WriteLine(Math.Abs(d - Math.Floor(d)) <= rounds*double.Epsilon);

// The difference is double.Epsilon, 4.940656458412465E-324
Console.WriteLine(Math.Abs(d - Math.Floor(d)).ToString("E15"));
...