Objective-C中浮点数表示时精度的小坑

本文迁移自本人简书账号酱油葱, 后续不会再在简书更新文章, 具体原因可以查看简书CEO盛赞程序员出轨率高“真实、新鲜、多元” ,对不起打扰了

发现问题

近日测试的童鞋告诉我, 你家APP的四舍五入有毛病. 明明是1.895 => 1.90, 为何出来的结果是1.89. 打死不信, 明明用的是四舍五入, 为何Format出来结果竟是如此. 于是花了小半天的时间, 才发现问题症结所在 -- [NSString stringWithFormat:]

原因

关于Format为什么不能作为四舍五入处理办法的描述

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中可以不用做什么理会, 但是如果考虑到数据不同精度的表达和计算时, 一定要充分考虑各种因素, 谨慎处理. 以免犯下阿波罗号的惨剧

鸣谢

感谢本司的测试童鞋和开发童鞋的支持, 发现并及时填上了这个坑.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值