金融类app各种数值的精确计算问题 ##
最近总算闲下来了,有时间把前段时间app里边踩到的坑总结下,既金融类app中有关数值精确计算的问题。
1.简单粗暴的方法—doubleValue解决
刚开始接手的项目中关于数值的计算就是按照字符串直接转doubleValue的方式转化为后进行加减乘除运算。这样做在大多数情况下是不会有问题的,但如果经过全面测试的话,有些字符串转double会出现精度损失问题,计算结果也会出现不准确的问题,一个最近踩过的坑就是用户再输入投资金额的时候会对自己账户中的可用余额进行判断,以确定余额是否充足,测试的时候输入很多数据都没问题,但线上用户大量数据就会出现偶尔数据错误,用户输入金额明明和可用余额一样,但判断的时候就会提示可用余额不足,老板使用过程中也遇到过,还找了我好几次,汗,都是之前开发人员的坑。所以记住这丫简单粗暴的方法针对金融类的对数据精度及其敏感的项目不可取。
2.明智之举—NSDecimalNumber
NSDecimalNumber是苹果提供的专门金融货币精确数值计算的API。
+ (instancetype)decimalNumberHandlerWithRoundingMode:(NSRoundingMode)roundingMode
scale:(short)scale
raiseOnExactness:(BOOL)raiseOnExactness
raiseOnOverflow:(BOOL)raiseOnOverflow
raiseOnUnderflow:(BOOL)raiseOnUnderflow
raiseOnDivideByZero:(BOOL)raiseOnDivideByZero
参数 | 说明 |
---|---|
roundingMode | 要使用的舍入模式,有四种值: NSRoundUp, NSRoundDown, NSRoundPlain, and NSRoundBankers |
scale | 结果保留几位小数 |
raiseOnExactness | 发生精确错误时是否抛出异常,一般为NO |
raiseOnOverflow | 发生溢出错误时是否抛出异常,一般为NO |
raiseOnUnderflow | 发生不足错误时是否抛出异常,一般为NO |
raiseOnDivideByZero | 被0除时是否抛出异常,一般为YES |
2.1 NSDecimalNumber 的使用。
NSDecimalNumber是进行数值计算的对象,ji所有的加减乘除,幂运算操作对象。
NSDecimalNumberHandler *handler = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundBankers
scale:2
raiseOnExactness:NO
raiseOnOverflow:NO
raiseOnUnderflow:NO
raiseOnDivideByZero:YES];
NSDecimalNumber *num1 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%@",@"2.37"]];
NSDecimalNumber *num2 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%@",@"15.89"]];
//加
NSDecimalNumber *addResult = [num1 decimalNumberByAdding:num2
withBehavior:handler];
//减
NSDecimalNumber *subtractResult = [num1 decimalNumberBySubtracting:num2
withBehavior:handler];
//乘
NSDecimalNumber *multiplyResult = [num1 decimalNumberByMultiplyingBy:num2
withBehavior:handler];
//除
NSDecimalNumber *divideResult = [num1 decimalNumberByDividingBy:num2
withBehavior:handler];
//平方
NSDecimalNumber *powerResult = [num1 decimalNumberByRaisingToPower:2
withBehavior:handler];
NSLog(@"\n%f\n%f\n%f\n%f\n%f",[addResult doubleValue],[subtractResult doubleValue],[multiplyResult doubleValue],[divideResult doubleValue],[powerResult doubleValue]);
这样就能够很好地解决金融类app货币单位精确计算的问题。但有一点需要注意的是如果计算公式比较长,那么withBehavior应该在最后一步使用,切不可在计算过程中进行舍入操作,相信有点数学常识的同学都能明白这个问题。
如下:等额本息的预计收益计算
* 公式: 每月本息还款额=(投资金额月利率(1+月利率)^还款月数)/(((1+月利率)^还款月数)-1)
/**
* 等额本息标的收益计算
* 公式: 每月本息还款额=(投资金额*月利率*(1+月利率)^还款月数)/(((1+月利率)^还款月数)-1)
* @param interest 年化收益
* @param investAmount 投资金额
* @param selectBid 选择的标
*
* @return 收益
*/
+ (double)calculatePrincipalAndInterestBidWithInvestInterest:(double)interest
amount:(NSString *)investAmount
bid:(Bid *)selectBid{
NSDecimalNumberHandler *roundBanker = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundBankers
scale:2.0f
raiseOnExactness:NO
raiseOnOverflow:NO
raiseOnUnderflow:NO
raiseOnDivideByZero:YES];
NSDecimalNumber *investAmountDec = [[NSDecimalNumber alloc] initWithString:investAmount];//投资金额
NSDecimalNumber *monthInterestDec = [[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%f",interest/100]] decimalNumberByDividingBy:[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%f",12.00]]];//月利率
double month = selectBid.creditPeriod;//标的募集期数
if (selectBid.creditType == CreditType_DaylyOneOffPayment) {
//按天标,转化为月份
month = month/30.00;
}
NSDecimalNumber *monthDec = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%f",month]];//还款月数
// * 公式: 每月本息还款额=(投资金额*月利率*(1+月利率)^还款月数)/(((1+月利率)^还款月数)-1)
NSDecimalNumber *decNum1 = [[[[[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%d",1]] decimalNumberByAdding:monthInterestDec] decimalNumberByRaisingToPower:month] decimalNumberByMultiplyingBy:monthInterestDec] decimalNumberByMultiplyingBy:investAmountDec];
NSDecimalNumber *decNum2 = [[[[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%d",1]] decimalNumberByAdding:monthInterestDec] decimalNumberByRaisingToPower:month] decimalNumberBySubtracting:[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%d",1]]];
NSDecimalNumber *monthIncomeDec = [decNum1 decimalNumberByDividingBy:decNum2];//月本息还款额
NSDecimalNumber *totalIncomeDec = [[monthDec decimalNumberByMultiplyingBy:monthIncomeDec] decimalNumberBySubtracting:investAmountDec withBehavior:roundBanker];//总还款额 - 本金 = 总收益
return [totalIncomeDec doubleValue] > 0 ? [totalIncomeDec doubleValue] : 0;
}