iOS-精度丢失

1,591 阅读2分钟

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

在项目开发中, 经常会和浮点数打交道, 那么肯定会遇到过精度的问题, 尤其是在电商 金融等领域的APP, 遇到的情况更是多, 例如

{
    "price" : 6.76
}

// 后台返回一个json数据,price是一个浮点数的价格
// APP接收的时候,一般都是如下处理的

NSString *res = @"{"price":6.76}"; 
NSDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:[res dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]];
NSLog(@"price = %@", [dic[@"price"] stringValue]);

// 打印出来的效果是: price = 6.75999999999999

NSDecimalNumber

其实对于这种情况,苹果是有一个专门的处理浮点数精度的类, 就是今天要说的NSDecimalNumber, 那么下面来简单介绍一下NSDecimalNumber的API.

加法

decimalNumberByAddingNSDecimalNumber 加法计算的一个API

NSDecimalNumber *number1 = [NSDecimalNumber decimalNumberWithString:@"15.99"];
NSDecimalNumber *number2 = [NSDecimalNumber decimalNumberWithString:@"29.99"];
NSDecimalNumber *res = [number1 decimalNumberByAdding:number2];    
NSLog(@"res: %@", res);
// 输出: res: 45.98
减法

decimalNumberBySubtractingNSDecimalNumber 减法计算的一个API

NSDecimalNumber *number1 = [NSDecimalNumber decimalNumberWithString:@"15.98"];
NSDecimalNumber *number2 = [NSDecimalNumber decimalNumberWithString:@"29.99"];
NSDecimalNumber *res = [number2 decimalNumberBySubtracting:number1];    
NSLog(@"res: %@", res);
// 输出: res: 14.01
乘法

decimalNumberByMultiplyingByNSDecimalNumber 乘法计算的一个API

NSDecimalNumber *number1 = [NSDecimalNumber decimalNumberWithString:@"15.98"];
NSDecimalNumber *number2 = [NSDecimalNumber decimalNumberWithString:@"29.99"];
NSDecimalNumber *res = [number1 decimalNumberByMultiplyingBy:number2];    
NSLog(@"res: %@", res);
// 输出: res: 479.2402
除法

decimalNumberByDividingByNSDecimalNumber 除法计算的一个API

NSDecimalNumber *number1 = [NSDecimalNumber decimalNumberWithString:@"15.5"];
NSDecimalNumber *number2 = [NSDecimalNumber decimalNumberWithString:@"29.9"];
NSDecimalNumber *res = [number2 decimalNumberByDividingBy:number1];    
NSLog(@"res: %@", res);
// 输出: res: 1.929032258064516

当然还有其他的API非常之强大, 这里仅仅列举咱们常用的一些, 如果感兴趣的话可以去查查对应的文档, 这几个方法对于我们电商的项目来说已经足够了.

处理方案:

最好的处理办法就是 直接让后台返回字符串类型的数字. 然后我们根据 NSDecimalNumber 写一个工具类, 用于处理浮点数精度处理.

  1. 直接让后台返回一个字符串,用NSDecimalNumber处理计算问题;
  2. 后台传浮点类型,用NSDecimalNumber处理精度丢失问题。
  • 这里我直接使用一个继承于NSObjec的工具类OPNumberTool来处理,实现如下:
@implementation OPNumberTool

+ (float)floatWithdecimalNumber:(double)num {
    return [[self decimalNumber:num] floatValue];
}

+ (double)doubleWithdecimalNumber:(double)num {
    return [[self decimalNumber:num] doubleValue];
}

+ (NSString *)stringWithDecimalNumber:(double)num {
    return [[self decimalNumber:num] stringValue];
}

+ (NSDecimalNumber *)decimalNumber:(double)num {
    NSString *numString = [NSString stringWithFormat:@"%lf", num];
    return [NSDecimalNumber decimalNumberWithString:numString];
}

@end

我们可以使用 OPNumberTool 来处理后台返回的浮点数数据,
我们使用文章开头的列子来做一下测试.

NSString *res = @"{"price":6.76}"; 
NSDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:[NSJSONSerialization JSONObjectWithData:[res dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil]];

NSString *price = [OPNumberTool stringWithDecimalNumber:[dic[@"price"] doubleValue]];
NSLog(@"price = %@", price);

运行的结果就是: price = 6.76, 到这里我们就已经大功告成了

结语

当我们在处理浮点数的时候, 尽量使用 double 来进行操作, 当然如果条件允许的话, 最好是后台返回 字符串类型的数字. 因为我接手的是别人的项目, 所以不可能因为精度的问题, 后台重新开发一遍数字类型, 只能自己默默处理了😭