XCTAssertEqualObjects 比较 NSNumber 的 NSArrays 失败,即使数组看起来相同
XCTAssertEqualObjects comparing NSArrays of NSNumber fails even though arrays appear identical
我正在 Objective-C 开发一个人工神经网络,所以我写了一些矩阵向量运算的方法。例如,下面是计算外积的代码。代码工作正常并且 returns 得到了想要的结果,但是在将方法 returned NSMutableArray
对象与单元测试中创建的对象进行比较时,我的单元测试失败了。我已经迷失了几天了。尽管对象看起来相同,但有人知道为什么 XCTAssertEqualObjects()
会失败吗?
这里是 return MLNNeuralNet.m 中 2 个向量 (NSArrays) 的外积的相关代码:
-(NSMutableArray *)outerProduct:(NSArray *)matrix1 by:(NSArray *)matrix2 {
/*Tensor Product of 2 vectors treated as column and row matrices, respectively*/
/*Example: if matrix1 is @[2, 4, 6] and matrix2 @[3, 4, 5], then calculation is:
[2 * 3, 2 * 4, 2 * 5], [4 * 3, etc...]
and result is:
@[@[6, 8, 10], @[12, 16, 20], @[18, 24, 30]]
*/
NSMutableArray *result = [[NSMutableArray alloc] init];
for (int i = 0; i < [matrix1 count]; i++) {
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
for (int j = 0; j < [matrix2 count]; j++) {
double product = [[matrix1 objectAtIndex:i] doubleValue] * [[matrix2 objectAtIndex:j] doubleValue];
[tempArray addObject:@(product)];
}
[result addObject:tempArray];
}
return result;
}
这是单元测试的代码:
@interface MLNNeuralNetTests : XCTestCase
@property (strong, nonatomic) MLNNeuralNet *neuralNet;
@end
@implementation MLNNeuralNetTests
- (void)setUp {
[super setUp];
_neuralNet = [[MLNNeuralNet alloc] init];
}
-(void)testOuterProduct {
NSMutableArray *matrix1 = [[NSMutableArray alloc] initWithArray:@[@(1.0), @(2.0), @(3.0)]];
NSMutableArray *matrix2 = [[NSMutableArray alloc] initWithArray:@[@(4.2), @(5.2), @(6.2)]];
NSMutableArray *layer1 = [[NSMutableArray alloc] initWithArray:@[@(4.2), @(5.2), @(6.2)]];
NSMutableArray *layer2 = [[NSMutableArray alloc] initWithArray:@[@(8.4), @(10.4), @(12.4)]];
NSMutableArray *layer3 = [[NSMutableArray alloc] initWithArray:@[@(12.6), @(15.6), @(18.6)]];
NSMutableArray *correctMatrix = [[NSMutableArray alloc]
initWithArray:@[layer1, layer2, layer3]];
NSMutableArray *testMatrix = [self.neuralNet outerProduct:matrix1 by:matrix2];
XCTAssertEqualObjects(correctMatrix, testMatrix, @"Matrix outer product failed");
}
这是我遇到的错误:
我认为这可能是由于我在单元测试版本中创建了 NSNumber 文字,例如 @(4.2) etc...
所以我尝试先创建 double
s,然后像这样包装在 NSNumber
中:
double number1 = 4.2;
NSMutableArray *layer1 = [[NSMutableArray alloc] initWithArray:@[@(number1), etc...
但这也没有用。
我是不是漏掉了什么?
当我尝试在类似测试中测试对象相等性时,我没有遇到任何问题。例如,以下测试不会失败:
-(void)testMultiplyVectorElements {
NSArray *vector1 = @[@(1.0), @(2.0), @(3.0), @(4.0)];
NSArray *vector2 = @[@(5.2), @(6.2), @(7.2), @(8.2)];
NSMutableArray *correctVector = [[NSMutableArray alloc] initWithArray:@[@(5.2), @(12.4), @(21.6), @(32.8)]];
NSMutableArray *testVector = [self.neuralNet multiplyVectorElements:vector1 by:vector2];
XCTAssertEqualObjects(correctVector, testVector, @"Vector element-wise multiplication failed.");
}
我相信这取决于浮点运算。浮点比较可能很棘手。如果这些是 "real" 数字,则组合它们的结果不会完全符合您的预期。
XCTAssertEqualObjects()
的输出使用 NSLog()
来打印 NSNumber
,这会四舍五入以供显示。您可以手动检查并查看更精确的值:
NSUInteger row_idx = 0;
for( NSArray<NSNumber *> * row in testMatrix ){
NSUInteger col_idx = 0;
for( NSNumber * testProduct in row ){
NSNumber * correctProduct = correctRow[row_idx][col_idx];
NSLog(@"%.16lf %.16lf", [product doubleValue], correctProduct);
/* This level of accuracy fails with your code. Drop at
* least one 0 to pass the assertion.
*/
XCTAssertEqualWithAccuracy([product doubleValue],
[correctProduct doubleValue],
0.000000000000001);
col_number += 1;
}
row_number += 1;
}
由此可见,本应相乘的12.6实际为12.6000000000000014,而correctMatrix
12.6中的字面量存储为12.5999999999999996。所以他们非常接近,但不是 ==
.
XCTAssertEqualWithAccuracy()
宏是为比较浮点值而设计的。它允许您传递第三个值以创建一个范围,在该范围内这些值被视为 "equal enough".
另一个选项,如果你正在做大量的数值 Cocoa 计算,是切换到 NSDecimalNumber
, 给出精确的 "real" 算术值。权衡是它比 NSNumber
更让人头疼,因为所有操作都通过方法。 [x decimalNumberByMultiplyingBy:y]
.
我正在 Objective-C 开发一个人工神经网络,所以我写了一些矩阵向量运算的方法。例如,下面是计算外积的代码。代码工作正常并且 returns 得到了想要的结果,但是在将方法 returned NSMutableArray
对象与单元测试中创建的对象进行比较时,我的单元测试失败了。我已经迷失了几天了。尽管对象看起来相同,但有人知道为什么 XCTAssertEqualObjects()
会失败吗?
这里是 return MLNNeuralNet.m 中 2 个向量 (NSArrays) 的外积的相关代码:
-(NSMutableArray *)outerProduct:(NSArray *)matrix1 by:(NSArray *)matrix2 {
/*Tensor Product of 2 vectors treated as column and row matrices, respectively*/
/*Example: if matrix1 is @[2, 4, 6] and matrix2 @[3, 4, 5], then calculation is:
[2 * 3, 2 * 4, 2 * 5], [4 * 3, etc...]
and result is:
@[@[6, 8, 10], @[12, 16, 20], @[18, 24, 30]]
*/
NSMutableArray *result = [[NSMutableArray alloc] init];
for (int i = 0; i < [matrix1 count]; i++) {
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
for (int j = 0; j < [matrix2 count]; j++) {
double product = [[matrix1 objectAtIndex:i] doubleValue] * [[matrix2 objectAtIndex:j] doubleValue];
[tempArray addObject:@(product)];
}
[result addObject:tempArray];
}
return result;
}
这是单元测试的代码:
@interface MLNNeuralNetTests : XCTestCase
@property (strong, nonatomic) MLNNeuralNet *neuralNet;
@end
@implementation MLNNeuralNetTests
- (void)setUp {
[super setUp];
_neuralNet = [[MLNNeuralNet alloc] init];
}
-(void)testOuterProduct {
NSMutableArray *matrix1 = [[NSMutableArray alloc] initWithArray:@[@(1.0), @(2.0), @(3.0)]];
NSMutableArray *matrix2 = [[NSMutableArray alloc] initWithArray:@[@(4.2), @(5.2), @(6.2)]];
NSMutableArray *layer1 = [[NSMutableArray alloc] initWithArray:@[@(4.2), @(5.2), @(6.2)]];
NSMutableArray *layer2 = [[NSMutableArray alloc] initWithArray:@[@(8.4), @(10.4), @(12.4)]];
NSMutableArray *layer3 = [[NSMutableArray alloc] initWithArray:@[@(12.6), @(15.6), @(18.6)]];
NSMutableArray *correctMatrix = [[NSMutableArray alloc]
initWithArray:@[layer1, layer2, layer3]];
NSMutableArray *testMatrix = [self.neuralNet outerProduct:matrix1 by:matrix2];
XCTAssertEqualObjects(correctMatrix, testMatrix, @"Matrix outer product failed");
}
这是我遇到的错误:
我认为这可能是由于我在单元测试版本中创建了 NSNumber 文字,例如 @(4.2) etc...
所以我尝试先创建 double
s,然后像这样包装在 NSNumber
中:
double number1 = 4.2;
NSMutableArray *layer1 = [[NSMutableArray alloc] initWithArray:@[@(number1), etc...
但这也没有用。
我是不是漏掉了什么?
当我尝试在类似测试中测试对象相等性时,我没有遇到任何问题。例如,以下测试不会失败:
-(void)testMultiplyVectorElements {
NSArray *vector1 = @[@(1.0), @(2.0), @(3.0), @(4.0)];
NSArray *vector2 = @[@(5.2), @(6.2), @(7.2), @(8.2)];
NSMutableArray *correctVector = [[NSMutableArray alloc] initWithArray:@[@(5.2), @(12.4), @(21.6), @(32.8)]];
NSMutableArray *testVector = [self.neuralNet multiplyVectorElements:vector1 by:vector2];
XCTAssertEqualObjects(correctVector, testVector, @"Vector element-wise multiplication failed.");
}
我相信这取决于浮点运算。浮点比较可能很棘手。如果这些是 "real" 数字,则组合它们的结果不会完全符合您的预期。
XCTAssertEqualObjects()
的输出使用 NSLog()
来打印 NSNumber
,这会四舍五入以供显示。您可以手动检查并查看更精确的值:
NSUInteger row_idx = 0;
for( NSArray<NSNumber *> * row in testMatrix ){
NSUInteger col_idx = 0;
for( NSNumber * testProduct in row ){
NSNumber * correctProduct = correctRow[row_idx][col_idx];
NSLog(@"%.16lf %.16lf", [product doubleValue], correctProduct);
/* This level of accuracy fails with your code. Drop at
* least one 0 to pass the assertion.
*/
XCTAssertEqualWithAccuracy([product doubleValue],
[correctProduct doubleValue],
0.000000000000001);
col_number += 1;
}
row_number += 1;
}
由此可见,本应相乘的12.6实际为12.6000000000000014,而correctMatrix
12.6中的字面量存储为12.5999999999999996。所以他们非常接近,但不是 ==
.
XCTAssertEqualWithAccuracy()
宏是为比较浮点值而设计的。它允许您传递第三个值以创建一个范围,在该范围内这些值被视为 "equal enough".
另一个选项,如果你正在做大量的数值 Cocoa 计算,是切换到 NSDecimalNumber
, 给出精确的 "real" 算术值。权衡是它比 NSNumber
更让人头疼,因为所有操作都通过方法。 [x decimalNumberByMultiplyingBy:y]
.