在做商城的时候,经常会涉及到价格,但是服务器一般给过来是数字类型 , json转换之后是NSNumber类型,在把NSNumber转成NSString显示到label.
绝大多数数字都没有问题,但是某些特殊的数字,比如 服务器给过来时 99.99 , 在json解析后也确实是99.99,但是打印log或者显示在label上变成了99.98999999999999 , 主要是系统是用二进制存贮10进制的数字 , 精度丢失无法避免 .
现在要想办法让展示出来 , 一开始想法是既然是价格那么肯定只会精确到分 , 所以判断2位小数就可以了 , 最初的想法 , 给NSNumber写一个category , 在类别中统一处理价格的显示 , 先获取这个数字的小数部分 , 如果小数部分的长度大于4,比如0.989999999这个的数字,那就要格式化字符串输出 %.2lf 即可 , 如果没有小数部分或者小数部分为0 , 输出原值即可.
方法1:
- (NSString *)moneyDescription {
double point = [self doubleValue] - [self integerValue] ;
NSString * pointStr = [NSString stringWithFormat:@"%lf",point];
if (pointStr.length > 4 && point>0) {
return [NSString stringWithFormat:@"%.2lf",self.doubleValue];
}
return self.stringValue;
}
本来这个也满足需求了,但是总觉得这样的方法有点傻傻的 , 一顿百度之后,找到了这个NSDecimalNumber, 顾名思义这是一个十进制数字类,继承自NSNumber,苹果针对浮点类型计算精度问题提供出来的计算类,基于十进制的科学计数法来计算,同时可以指定舍入模式,一般用于货币计算。果然还是苹果的开发已经考虑到NSNumber显示的进度丢失. 所以推荐使用方法2.
方法2
- (NSString *)moneyDescription {
double conversionValue = [self doubleValue];
NSString *doubleString = [NSString stringWithFormat:@"%lf", conversionValue];
NSDecimalNumber *decNumber = [NSDecimalNumber decimalNumberWithString:doubleString];
return decNumber.description;
}
实现之后就在想 , 现在要在每个价格上改一遍用moneyDescription , 为什么不用runtime替换下系统原来的打印方式.
+ (void)load{
[self swizzleInstanceMethod:@selector(description) with:@selector(moneyDescription)];
}
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if (!originalMethod || !newMethod) return NO;
class_addMethod(self,
originalSel,
class_getMethodImplementation(self, originalSel),
method_getTypeEncoding(originalMethod));
class_addMethod(self,
newSel,
class_getMethodImplementation(self, newSel),
method_getTypeEncoding(newMethod));
method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
class_getInstanceMethod(self, newSel));
return YES;
}
替换之后,发现 NSNumber的 description方法 , 在NSLog中没有调用 , 在 [NSString stringWithFormat:@"¥ %@",num] 也是无效的 , 一顿百度+问其他大神 , 发现[NSString stringWithFormat:@"¥ %@",num] 会调用 descriptionWithLocale , 但是NSLog不知道会调到什么方法 , 所以既然写的都是用stringWithFormat , 那就转发到description , (其实local里面什么都没有)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
- (NSString *)descriptionWithLocale:(id)locale{
// local中什么都没有,直接消息转发到description
//NSLog(@"locale is %@",locale);
return [self description];
}
#pragma clang diagnostic pop
最后附上完成的类别文件
#import <Foundation/Foundation.h>
@interface NSNumber (EqualString)
@property (nonatomic,readonly,copy) NSString * moneyDescription ;
- (BOOL)isEqualToString:(NSString *)aString;
@end
#import "NSNumber+EqualString.h"
#import <objc/objc.h>
#import <objc/runtime.h>
@implementation NSNumber (EqualString)
- (BOOL)isEqualToString:(NSString *)aString {
return [self.description isEqualToString:aString];
}
- (NSString *)moneyDescription {
// double point = [self doubleValue] - [self integerValue] ;
// NSString * pointStr = [NSString stringWithFormat:@"%lf",point];
// if (pointStr.length > 4 && point>0) {
// return [NSString stringWithFormat:@"%.2lf",self.doubleValue];
// }
// return self.stringValue;
NSLog(@"moneyDescription");
double conversionValue = [self doubleValue];
NSString *doubleString = [NSString stringWithFormat:@"%lf", conversionValue];
NSDecimalNumber *decNumber = [NSDecimalNumber decimalNumberWithString:doubleString];
return decNumber.description;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
// [NSString stringWithFormat:@"¥ %@",num] , 会调到这个方法
- (NSString *)descriptionWithLocale:(id)locale{
NSLog(@"locale is %@",locale);
return [self description];
}
#pragma clang diagnostic pop
// num.description直接调用才管用 ; [NSString stringWithFormat:@"¥ %@",num] , 不管用
- (NSString *)description {
NSLog(@"description");
double point = [self doubleValue] - [self integerValue] ;
NSString * pointStr = [NSString stringWithFormat:@"%lf",point];
if (pointStr.length > 4 && point>0) {
return [NSString stringWithFormat:@"%.2lf",self.doubleValue];
}
return self.stringValue;
}
// po 的时候调用
- (NSString *)debugDescription {
NSLog(@"debugDescription");
double point = [self doubleValue] - [self integerValue] ;
NSString * pointStr = [NSString stringWithFormat:@"%lf",point];
if (pointStr.length > 4 && point>0) {
return [NSString stringWithFormat:@"%.2lf",self.doubleValue];
}
return self.stringValue;
}
+ (void)load{
[self swizzleInstanceMethod:@selector(description) with:@selector(moneyDescription)];
}
+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
Method originalMethod = class_getInstanceMethod(self, originalSel);
Method newMethod = class_getInstanceMethod(self, newSel);
if (!originalMethod || !newMethod) return NO;
class_addMethod(self,
originalSel,
class_getMethodImplementation(self, originalSel),
method_getTypeEncoding(originalMethod));
class_addMethod(self,
newSel,
class_getMethodImplementation(self, newSel),
method_getTypeEncoding(newMethod));
method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
class_getInstanceMethod(self, newSel));
return YES;
}
@end