处理服务器的json中的数字显示价格

本文介绍了解决NSNumber在处理价格时精度丢失的问题,通过使用NSDecimalNumber进行十进制计算,确保货币计算的准确性,并提供了自定义方法和runtime替换技巧。

在做商城的时候,经常会涉及到价格,但是服务器一般给过来是数字类型 , 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

在实际使用中,有时候还是会遇到需要四舍五入保留几位的情况,那么就需要这个, scale是最多保留的位数,roundingMode是怎么进行四舍五入,
有上溢 1.11->1.2 ,
有舍位 1.11->1.1
正常四舍五入 1.11->1.1
还有一个不太常用而且比较奇怪,就不写了

/**
 *  @brief  四舍五入 NSRoundPlain
 *
 *  @param scale 限制位数
 *
 *  @return 返回结果
 */
- (NSDecimalNumber *)jk_roundToScale:(NSUInteger)scale{
    return [self jk_roundToScale:scale mode:NSRoundPlain];
}
/**
 *  @brief  四舍五入的方式
 *
 *  @param scale        限制位数
 *  @param roundingMode NSRoundingMode
 *
 *  @return 返回结果
 */
- (NSDecimalNumber *)jk_roundToScale:(NSUInteger)scale mode:(NSRoundingMode)roundingMode{
  NSDecimalNumberHandler * handler = [[NSDecimalNumberHandler alloc] initWithRoundingMode:roundingMode scale:scale raiseOnExactness:NO raiseOnOverflow:YES raiseOnUnderflow:YES raiseOnDivideByZero:YES];
  return [self decimalNumberByRoundingAccordingToBehavior:handler];
}

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值