NSNumber compare: 返回不同的结果

7

我正在尝试进行一些数字比较,但结果很奇怪。

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?


3
如果你只是想确定两个数的值是否相等,那么使用[number1 isEqualToNumber:number2]是首选方式。 - twilson
1
抱歉,twilson,仍然返回NO。数字是不同的。 - picciano
4个回答

7

我尝试使用isEqualToNumber:,但它返回了NO。它们不相同的原因是因为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

非常好的解决方案!我觉得苹果不使用compare:方法来处理这个问题有点疯狂... - Matt W.
5
苹果(或更准确地说,编译器的编写者)不能假定程序员对相等性的容忍程度。他们所能做的就是按程序员告诉他们要做的去做。在这种情况下,您正在比较两个不相等的值。浮点数并非一个简单的主题。如果您有时间,请查看《计算机科学家应该了解的浮点算术知识》(What Every Computer Scientist Should Know About Floating-Point Arithmetic)。 - SSteve
谢谢你提供的参考!我一直在寻找关于这个问题的推荐读物。我已经知道这个问题有一段时间了,只是一直没有采取行动。 :) - Matt W.
这是正确的解决方案,但我想补充一点,通过将简单算法捆绑到类别中的-[NSNumber isEqualToNumber:withTolerance:]方法中,可以使其更易读。 - Stephen

2

compare:方法遵循标准C语言的类型转换规则。例如,如果您比较拥有整数值的NSNumber对象和拥有浮点值的NSNumber对象,则会将整数值转换为浮点值进行比较。

此外,使用float初始化NSNumber,然后将其转换为double会丢失一些精度。

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

如果你知道可能会有浮点数和双精度浮点数的混合,一种解决方法是像你在问题代码片段的最后一行所做的那样比较NSNumber的floatValues。

此外,你可以使用objCType方法获取NSNumber中数据的类型。


我真的很喜欢那个 objType!谢谢你! - Matt W.

2

请尝试使用isEqualToNumber方法来检查您的条件。

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

这个stackoverflow问题也给出了一个详细的答案。


这不会按预期工作,会记录“NO”。使用float和double初始化的NSNumbers将是不同的值。请参见我上面的答案。你引用的SO问题使用的都是浮点数。 - picciano

0
另一个对我有效的解决方案是将它们转换为NSString并进行字符串比较。这是因为我正在处理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];
}

这样就解决了我比较一个带有两个尾随零的双精度浮点数和一个没有尾随零的单精度浮点数的问题。

通常我会警告不要像这样进行字符串比较,但由于我的规则始终是固定的,在这种情况下对我来说是可以的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接