Сравнение NSNumber: возвращаются разные результаты

Я пытаюсь сделать некоторые сравнения чисел, и я получаю некоторые странные результаты.

NSNumber* number1 = [NSNumber numberWithFloat:1.004];
NSNumber* number2 = [NSNumber numberWithDouble:1.004];

([number1 compare:number2] == NSOrderedSame) ? NSLog(@"YES") : NSLog(@"NO");
([number1 compare:number2] == NSOrderedAscending) ? NSLog(@"YES") : NSLog(@"NO");
([number1 doubleValue] == [number2 doubleValue]) ? NSLog(@"YES") : NSLog(@"NO");
([number1 floatValue] == [number2 floatValue]) ? NSLog(@"YES") : NSLog(@"NO");

Вывод журнала:

НЕТ
ДА
НЕТ
ДА

Это очень расстраивает меня. Я знаю, что это, вероятно, потому, что разница между количеством битов в поплавке по сравнению с двойным. Мне кажется, что для сравнения он усекает двойное число до числа с плавающей запятой. Но если я не знаю, как создается число, как мне получить правильные результаты? Есть ли другой способ сравнить NSNumber?


person Matt W.    schedule 09.02.2012    source источник
comment
А если вы используете [number1 isEqualToNumber:number2], это предпочтительный способ, по крайней мере, если вы пытаетесь только установить равенство значений?   -  person twilson    schedule 10.02.2012
comment
Извините, twilson, это все еще возвращает НЕТ. Цифры РАЗНЫЕ.   -  person picciano    schedule 10.02.2012


Ответы (4)


Я попытался использовать isEqualToNumber:, и он вернул НЕТ. Причина, по которой они не совпадают, заключается в том, что 1.004 не может быть точно представлено в двоичном виде. Приближение double имеет больше цифр после запятой, чем приближение float, поэтому эти два числа различны. Обычно при сравнении чисел с плавающей запятой вы проверяете, равны ли они в пределах допустимого значения fabs(a - b) < tolerance:

NSNumber* number1 = [NSNumber numberWithFloat:1.004];
NSNumber* number2 = [NSNumber numberWithDouble:1.004];

NSLog(@"number 1: %.12f", [number1 doubleValue]);
NSLog(@"number 2: %.12f", [number2 doubleValue]);

([number1 isEqualToNumber:number2]) ? NSLog(@"YES") : NSLog(@"NO");
fabs([number1 floatValue] - [number2 doubleValue]) < 1.0e-7 ? NSLog(@"YES") : NSLog(@"NO");

полученные результаты:

2012-02-09 15:08:34.272 so9219935[3313:903] number 1: 1.003999948502
2012-02-09 15:08:34.274 so9219935[3313:903] number 2: 1.004000000000
2012-02-09 15:08:34.275 so9219935[3313:903] NO
2012-02-09 15:08:34.275 so9219935[3313:903] YES
person SSteve    schedule 09.02.2012
comment
Очень хорошее решение! Мне кажется сумасшедшим, что Apple не справляется с этим с помощью метода compare:... - person Matt W.; 10.02.2012
comment
Apple (или, точнее, авторы компилятора) не могут делать предположения о терпимости программиста к равенству. Все, что они могут сделать, это именно то, что сказал им программист. В этом случае вы сравнивали два неравных значения. Числа с плавающей запятой — нетривиальная тема. Если у вас есть время, взгляните на Что должен делать каждый программист Узнайте об арифметике с плавающей запятой. - person SSteve; 10.02.2012
comment
Спасибо за ссылку! Я искал рекомендуемое чтение по этому поводу. Я знал об этой проблеме некоторое время, просто ничего не предпринимал по этому поводу. :) - person Matt W.; 10.02.2012
comment
Это правильное решение, но я бы добавил, что его можно сделать более читабельным, объединив этот простой алгоритм в метод -[NSNumber isEqualToNumber:withTolerance:] в категории класса. - person Stephen; 26.02.2016

пожалуйста, попробуйте isEqualToNumber: метод, чтобы проверить свое состояние

([number1 isEqualToNumber:number2]) ? NSLog(@"YES") : NSLog(@"NO");

этот вопрос stackoverflow также дает подробный ответ

person carbonr    schedule 09.02.2012
comment
Это не будет работать должным образом и будет регистрироваться NO. NSNumbers, инициализированные с помощью float и double, будут разными значениями. Смотрите мой ответ выше. Вопрос SO, на который вы ссылаетесь, использует все поплавки. - person picciano; 10.02.2012

Метод compare: следует стандартным правилам C для преобразования типов. Например, если вы сравниваете объект NSNumber, который имеет целочисленное значение, с объектом NSNumber, который имеет значение с плавающей запятой, целочисленное значение преобразуется в значение с плавающей запятой для сравнения.

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

NSNumber* number1 = [NSNumber numberWithFloat:1.004];
NSNumber* number2 = [NSNumber numberWithDouble:1.004];
NSLog(@"number1 double: %1.16f", [number1 doubleValue]);
NSLog(@"number2 double: %1.16f", [number2 doubleValue]);
NSLog(@"number1 objCType %s", [number1 objCType]);
NSLog(@"number2 objCType %s", [number2 objCType]);

2012-02-09 15:59:49.487 testNSNumber[89283:f803] number1 double: 1.0039999485015869
2012-02-09 15:59:49.488 testNSNumber[89283:f803] number2 double: 1.0040000000000000
2012-02-09 16:21:01.655 testNSNumber[4351:f803] number1 objCType f
2012-02-09 16:21:01.656 testNSNumber[4351:f803] number2 objCType d

Если вы знаете, что у вас может быть сочетание float и double, одним из решений является сравнение NSNumber floatValues, как вы сделали в последней строке фрагмента кода в своем вопросе.

Кроме того, вы можете получить тип данных в NSNumber с помощью метода objCType.

person picciano    schedule 09.02.2012
comment
Мне очень нравится этот objType! Благодарю вас! - person Matt W.; 10.02.2012

Другое решение, которое сработало для меня, состояло в том, чтобы преобразовать их в NSStrings и выполнить сравнение строк. Это было потому, что я работаю с 4-значными десятичными ценами, и это хорошо сработало для меня.

-(BOOL) hasPriceChanged{
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    formatter.numberStyle = NSNumberFormatterDecimalStyle;
    formatter.maximumFractionDigits = 4;
    NSString *p = [formatter stringFromNumber:self.price];
    NSString *op = [formatter stringFromNumber:self.originalPrice];
    return ![p isEqualToString:op];
}

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

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

person Guy Lowe    schedule 09.11.2018