金额计算精确度问题

金额相关内容的计算,对精确要求较高,不应该使用float和double

2015-09-21 | 阅读

使用BigDecimal,而不是使用float或double

floatdouble 类型,执行的是二进制浮点运算,是为了在广泛数值范围上提供较为精确的快速近似计算而精心设计的,不应该被用于需要精确结果的场合,也 尤其不适合用于货币计算, 因为要让一个float或double类型精确地表示0.1(或10的任何其他负次方值)是不可能的.

代码逻辑中有时要对这个不精确的数值进行判断,会导致一些逻辑的异常:

float funds = 1.00;
for (float price = 0.10; funds >= price; price+= 0.10) {
funds -= price;
}
NSLog(@"leave money: %f",funds );

1.00 = 0.10 + 0.20 + 0.30 + 0.40 ,测试输出结果为:

`leave money: 0.400000`

所以,对于使用floatdouble时,如果作为判断条件,一定要加上精确度的判断,否则就会出现逻辑的错误.

高精度计算

我们有三种方式:

  • 使用floatdouble。需要注意,对于条件判断需要判断精度,而输出时需要进行舍入。
  • 使用long或者int,定制小数点以及输出转换.
  • 使用系统提供的建议类型NSDecimalNumber.

简单介绍NSDecimalNumber

// 由字符串进行初始化.
NSDecimalNumber* a = [NSDecimalNumber decimalNumberWithString:@"1.23456"];
// 由unsigned long long类型进行初始化,exponent为位数,isNegative为是否为负数.
NSDecimalNumber* b= [NSDecimalNumber decimalNumberWithMantissa:246890 exponent:-2 isNegative:YES];
//加减乘除操作:
a = [a decimalNumberByAdding:b];
a = [a decimalNumberBySubtracting:b];//减为 a-b
a = [a decimalNumberByMultiplyingBy:b];
a = [a decimalNumberByDividingBy:b];//除为 a / b
// n次 幂操作
a = [a decimalNumberByRaisingToPower:2];

比较操作,返回一个NSComparisonResult类型的枚举,即以-1为小于,1为大于,0为相等.

typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};

[a compare:b] //进行比较.需要注意的是,compare函数的参数是 NSNumber,所以,要比较其他数值也就比较简单,如要与基础类型进行比较:
[a compare: @1];//NSNumber可以用@进行初始化.

float和double不精确的原因

因为float和double底层是二进制数据,二进制在表达整数时,是很靠谱的,但是表达小数时就不行了,根本原因是位数不够用,简单表现是 二进制的小数无法精确的表现任何一个数字.

如float类型,它的范围为 : 1.175494351 E – 38 - 3.402823466 E + 38 在这个范围内,大概有1 E 38个值,但是float只有4个字节2 ^32个不同的值,也就是只有约 4 * 10 ^ 9个确定值,这就差了非常多了,所以要将这点数量的确定值放在 这么大的范围内,肯定是有一堆数字无法表示的.

如,我们随便输出一个float :

float a = 124.56;
NSLog(@"%f",a);

命令行中输出的是: 124.559998, 所以用这个数字去计算和判断,就会出现错误。