金额相关精度问题

最近项目中遇到金额计算,CGFloat类型金额计算的过程中,让我吃了不少苦头,先来看下面几行代码:

示例代码

    CGFloat var1 = 120.66;
    CGFloat var2 = 120.66;
    
    CGFloat var3 = [@"120.66" floatValue];
    CGFloat var4 = [@"120.66" floatValue];
    
    CGFloat var5 = 120.67 - 0.01;
    
    CGFloat var6 = [@"120.67" floatValue] - 0.01;
    
    bool flag1 = var1 == var2;
    bool flag2 = var3 == var4;
    bool flag3 = var3 == var5;
    bool flag4 = var5 == var6;
    bool flag5 = var3 == var6;

略去猜答案的过程,直接看结果

图一
图一

现象总结

  • var1 和var2相等
  • var3 和var4相等
  • NSString 的 floatValue 会让CGFloat 的精度增加
  • 精度确定的float类型做运算,精度确定 var5 = 120.66
  • 不同精度的float类型做运算,精度会丢失 var6 = 120.65999816894531

浮点数相等问题

    CGFloat x = var5 - var6;  
    const CGFloat EPSINON = 0.000001;  
    if (( x >= -EPSINON ) && ( x <= EPSINON ))  
    {  
        NSLog("var5 和var6相等");  
    } 

鉴于CGFloat在处理金额过程中会遇到精度问题,iOS中提供了NSDecimalNumber类来处理精度问题,如果要四舍五入的话还要使用另一个类 NSDecimalNumberHandler

NSDecimalNumber类

/*
     讲述下参数的含义:
     RoundingMode: 简单讲就是你要四舍五入操作的标准.
typedef NS_ENUM(NSUInteger, NSRoundingMode) {
    NSRoundPlain,   // 精度位后四舍五入
    NSRoundDown,    // 精度位后舍去
    NSRoundUp,      // 精度位后入上去
    NSRoundBankers  // 精度位后的数值等于5时,分两种情况:5后面还有其他数字(非0),则进位后舍去;若5后面是0(即5是最后一位),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。
};      
    // Rounding policies :
    // Original
    //    value 1.2  1.21  1.25  1.35  1.27
    // Plain    1.2  1.2   1.3   1.4   1.3
    // Down     1.2  1.2   1.2   1.3   1.2
    // Up       1.2  1.3   1.3   1.4   1.3
    // Bankers  1.2  1.2   1.2   1.4   1.3

     scale : 需要保留的精度。
     raiseOnExactness : 为YES时在处理精确时如果有错误,就会抛出异常。
     raiseOnOverflow  : YES时在计算精度向上溢出时会抛出异常,否则返回。
     raiseOnUnderflow : YES时在计算精度向下溢出时会抛出异常,否则返回.
     raiseOnDivideByZero : YES时。当除以0时会抛出异常,否则返回。
     */
     
     保留2位小数
NSDecimalNumberHandler *roundUp = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain
                                                                                            scale:2
                                                                                 raiseOnExactness:NO
                                                                                  raiseOnOverflow:NO
                                                                                 raiseOnUnderflow:NO
                                                                              raiseOnDivideByZero:YES];                                                                                                                                                 

计算金额并四舍五入

NSDecimalNumber *originMoney = [NSDecimalNumber decimalNumberWithString:num];//通过字符串计算金额
NSDecimalNumber *roundMoney = [subtotal decimalNumberByRoundingAccordingToBehavior:roundUp];//按照设定的规则计算出金额

金额计算实例

NSDecimalNumber *varDecimal1 = [NSDecimalNumber decimalNumberWithString:@"120.661"];//120.661
NSDecimalNumber *varDecimal2 = [NSDecimalNumber decimalNumberWithString:@"120.670"];//120.670

//加
[varDecimal1 decimalNumberByAdding:varDecimal2];

//减
NSDecimalNumber *varDecimal3 = [[varDecimal2 decimalNumberBySubtracting:varDecimal1] decimalNumberByRoundingAccordingToBehavior:roundUp];//0.01

//乘
[varDecimal1 decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithString:@"0.005"]];

//除
[varDecimal1 decimalNumberByDividingBy:varDecimal2];

//比较
[varDecimal1 compare:varDecimal2] == NSOrderedAscending //varDecimal1小于varDecimal2

总结

  1. 金额相关,后台要传字符串到前台
  2. 计算过程中要用NSDecimalNumber来防止计算过程中出现精度错误
  3. [@"120.67" floatValue] - 0.01 不等于120.66
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容