Проверить, является ли число с плавающей запятой целым числом

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

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

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


person BCS    schedule 26.09.2008    source источник


Ответы (12)


d == Math.Floor(d)

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

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

person Community    schedule 26.09.2008
comment
Не совсем то же самое. Я думаю, что это намного лучше. В случае больших чисел inetegr, которые можно хранить в double, но нельзя хранить в int, приведение к int работать не будет. - person Maciej Hehl; 27.09.2008
comment
Как вы упомянули, следует проявлять большую осторожность при любом сравнении равенства с плавающей запятой - в большинстве случаев гораздо лучшей стратегией является использование небольшого коэффициента эпсилон. - person Cade Roux; 27.09.2008
comment
Мне нравится ответ! Однако в моих случаях бывает, что я также должен обнаруживать переполнение (или любую ошибку округления, если на то пошло). Я оставлю вопрос в более общей форме. - person BCS; 27.09.2008
comment
@Cade Roux: точное сравнение FP полезно, если вы смотрите на действия округления. - person BCS; 27.09.2008
comment
@BCS, конечно - просто нужно помнить, что == означает действительно равно - у меня были проблемы с датами в VB из-за представления с плавающей запятой - разница, которую VB даже не может отобразить. - person Cade Roux; 27.09.2008
comment
@Mike F: в альтернативной стратегии будет использоваться d == Math.Truncate(d), поскольку здесь используется более простой режим округления. - person user7116; 01.10.2008

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

if (d % 1 == 0) {
  //...
}
person VoxPelli    schedule 26.09.2008
comment
Я просто немного устал от мода int с отрицательными числами, я не уверен, что хочу вычислять их для FP. - person BCS; 27.09.2008
comment
@BCS, не могли бы вы сказать, что не так с модом int с отрицательными числами? - person user1234567; 01.08.2020
comment
Существует по крайней мере две разные интерпретации целочисленных модулей, когда один или оба операнда отрицательны. И есть много ошибок в коде из-за того, что люди не помнят, какой язык использует текущий. Я не знаю, что произойдет, если вы тоже в FP. И я подозреваю, что ответ слишком разнообразен, чтобы его можно было безопасно использовать. Например, без детального прочтения стандартов я бы не был уверен, что в некоторых случаях (или в какой-то версии языка) FP не будет приведен к int перед модом. - person BCS; 03.08.2020

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

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

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

person Khoth    schedule 26.09.2008
comment
@Khoth Пожалуйста, не могли бы вы продемонстрировать случай, когда (d==Math.Floor(d)) != (d==Math.Floor(d+0,00001)) ? - person aka.nice; 21.08.2012
comment
Вы должны использовать double.Epsilon... Или, если вы рассматриваете x много операций x*double.Epsilon. - person ccook; 19.11.2012

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

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

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

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

Вот, 1.1 + 0.6 - 1.7 == 2.2204460492503131e-16.

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

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

person ddaa    schedule 26.09.2008
comment
Хороший момент и хорошо описанный. Однако в моем случае желаемое поведение хорошо определяется исходным кодом. Все, что имитирует его, допустимо. - person BCS; 27.09.2008
comment
Даже 0,1 != 0,1, если речь идет о числах с плавающей запятой. Мантисы обязательно усекают двоичное значение 0.0001100110011001100110011001100... Попробуйте 0.1F==0.1 - person ccook; 19.11.2012

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

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

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

person yonil    schedule 21.02.2013

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

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

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

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

person Bill K    schedule 26.09.2008
comment
Или is_almost_integer(d,eps) может быть просто return Math.Floor(d-eps)!=Math.Floor(d+eps) - person aka.nice; 21.08.2012

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

if (d == (int)d) {
 //...
}
person swilliams    schedule 26.09.2008
comment
Это работает! Кстати, вы уверены, что это будет точно так же? - person BCS; 27.09.2008
comment
Эм. Как насчет этого? d= 3e30; if (d == (int)d) { - person tzot; 27.09.2008
comment
Да, он не будет делать именно то, о чем я просил, но я сделаю то, что мне нужно. - person BCS; 27.09.2008

Используйте Math.Truncate()

person Michał Piaskowski    schedule 26.09.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
person loudej    schedule 26.09.2008

Что-то вроде этого

double d = 4.0;
int i = 4;

bool equal = d.CompareTo(i) == 0; // true
person Darren Kopp    schedule 26.09.2008

Не могли бы вы использовать это

    bool IsInt(double x)
    {
        try
        {
            int y = Int16.Parse(x.ToString());
            return true;
        }
        catch 
        {
            return false;
        }
    }
person Crash893    schedule 28.02.2009
comment
int.TryParse было бы проще использовать, но это также потребует (относительно) больших накладных расходов. - person Hosam Aly; 28.02.2009
comment
Подача x.ToString() в некоторую логику поиска строк может быть даже лучше. - person BCS; 28.02.2009
comment
Я думаю, вы могли бы сделать что-то вроде if(x.tostring().indexof('.')!= -1) {true} else {false} - person Crash893; 28.02.2009
comment
@ Crash893: вам понадобится IndexOfAny('.', 'e', ​​'E'), так как некоторые значения не имеют точек (например, 1e15). - person Hosam Aly; 01.03.2009

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

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"));
person ccook    schedule 18.11.2012