本文迁移自本人简书账号酱油葱, 后续不会再在简书更新文章, 具体原因可以查看简书CEO盛赞程序员出轨率高“真实、新鲜、多元” ,对不起打扰了
发现问题
近日测试的童鞋告诉我, 你家APP的四舍五入有毛病. 明明是1.895 => 1.90, 为何出来的结果是1.89. 打死不信, 明明用的是四舍五入, 为何Format出来结果竟是如此. 于是花了小半天的时间, 才发现问题症结所在 -- [NSString stringWithFormat:]
原因
You should only ever do this while formatting a number for display to the end user, because there is no guarantee that.
类同于printf
不保证实现四舍五入, [NSString stringWithFormat:]
方法也不能保证实现四舍五入, 作为数字描述信息的展示, 永远只能是近似, 更何况浮点型数据在计算机存储的精度是不准确且有限的.
/********************************************************************************
Base floating point types
Float32 32 bit IEEE float: 1 sign bit, 8 exponent bits, 23 fraction bits
Float64 64 bit IEEE float: 1 sign bit, 11 exponent bits, 52 fraction bits
Float80 80 bit MacOS float: 1 sign bit, 15 exponent bits, 1 integer bit, 63 fraction bits
Float96 96 bit 68881 float: 1 sign bit, 15 exponent bits, 16 pad bits, 1 integer bit, 63 fraction bits
Note: These are fixed size floating point types, useful when writing a floating
point value to disk. If your compiler does not support a particular size
float, a struct is used instead.
Use of of the NCEG types (e.g. double_t) or an ANSI C type (e.g. double) if
you want a floating point representation that is natural for any given
compiler, but might be a different size on different compilers.
*********************************************************************************/
复制代码
Cocoa框架针对浮点基本类型也有详细的描述, 超过7位(包括小数点整数部分)的Float32数精度本身已不可靠, 如果再依赖此数据进行format
, 结果可想而知.
解决思路
浮点数据处理的正确做法参见Wiki 既然浮点型数据的存储和计算自有其规则, 自然应该按照规则来办事, 才能得出正确的结果. 作为成熟的Cocoa框架, 必然有针对性的API做浮点型数据的Format处理. 以下是改造的内容:
@implementation ODMNumberFormatter
+ (ODMNumberFormatter *)shareInstance {
static ODMNumberFormatter *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[ODMNumberFormatter alloc] init];
instance.numberFormatter = [[NSNumberFormatter alloc] init];
instance.numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
instance.numberFormatter.roundingMode = kCFNumberFormatterRoundHalfUp;
});
return instance;
}
+ (NSString *)roundDecimalWithFormat:(NSString *)format value:(double)value {
NSNumberFormatter *numberFormatter = [self shareInstance].numberFormatter;
numberFormatter.positiveFormat = format;
return [numberFormatter stringFromNumber:[NSNumber numberWithDouble:value]];
}
@end
复制代码
结果
测试用例如下:
NSLog(@"距离为:%@", [ODMNumberFormatter roundDecimalWithFormat:@"0.##" value:1.995]);
NSLog(@"距离为:%@", [ODMNumberFormatter roundDecimalWithFormat:@"0.00" value:1.894]);
NSLog(@"距离为:%@", [ODMNumberFormatter roundDecimalWithFormat:@"0.00" value:1.895]);
NSLog(@"距离为:%@", [ODMNumberFormatter roundDecimalWithFormat:@"0.00" value:1.899]);
NSLog(@"距离为:%@", [ODMNumberFormatter roundDecimalWithFormat:@"0.00" value:1.900]);
复制代码
测试结果如下:
距离为:2
距离为:1.89
距离为:1.90
距离为:1.90
距离为:1.90
复制代码
结论
数据处理在不要求精度的APP中可以不用做什么理会, 但是如果考虑到数据不同精度的表达和计算时, 一定要充分考虑各种因素, 谨慎处理. 以免犯下阿波罗号的惨剧
鸣谢
感谢本司的测试童鞋和开发童鞋的支持, 发现并及时填上了这个坑.