iOS小数去除末位无效零问题

来源:互联网 发布:java gzip 压缩 编辑:程序博客网 时间:2024/05/21 09:21

iOS小数去除末位无效零问题

Ps:直接不想看过程的请直接跳到2.4和3.2

0.简述

本文针对以下情况做讨论并进行解决

1.问题描述

在开发时我们会经常遇到这么一个问题,即在显示一个数字的时候,我们希望将它后面的的无效零位去掉

2.解决办法

2.1 对于确定小数的精度的位数,非常简单,直接使用%.nf语法,n能限定小数后精确到几位

double value = 0.98000;NSString *str = [NSString stringWithFormat:@"%.2lf",value];打印结果为0.98
Problem: 但有时候小数点后面位数不确定,这么做的话会引发精度缺失问题,如
double value = 0.98125;NSString *str = [NSString stringWithFormat:@"%.2lf",value];打印结果为0.98,丢失了0.125

2.2 对于2.1的Problem,有个非常简便的方法,即调用NSNumber的description方法

double value1 = 0.98000;double value2 = 0.98125;NSLog(@"%@",@(value1).description);NSLog(@"%@",@(value2).description);打印结果为0.98和0.98125
Problem: 但有时候@(double).description方法会有精度问题,如
double value = 0.07;NSLog(@"%@",@(value).description);结果为0.07000000000000001double value = 8.45;NSLog(@"%@",@(value).description);其结果为8.449999999999999

2.3 而对于2.2的Problem,我们固然可以通过数学方法,确定一个精度,然后在精度范围内再对其进行转化,然后再进行处理来得到想要的NSString,但,这么做未免太过麻烦,其实有个取巧的办法,就是通过将double转化为float再调用description方法

double value = 0.07;NSLog(@"%@",@((float)value).description);结果为0.07double value = 8.45;NSLog(@"%@",@((float)value).description);其结果为8.45
Problem: 我们什么时候需要进行转换呢?

2.4 而对于2.3的Problem,我们观察一下double结构(1位符号位,11位指数位,52位尾数位),不难发现其精度有效位(在数据没有大到or小到用科学计数法的情况下)为log(2^52)/log(10) = 15.6535597 位(10进制位),那么,在加上小数点位,其值至少为16位,那么我们就可以用16为判断依据

PS:0.07000000000000001的实际构成为:(精度位)0.7000000000000001 * (计数位)10^-1
double value = 0.07;NSString *str = @(value).description;if (str.length >= 16) {    str = @((float)value).description;}NSLog(@"%@",str);    结果为0.07value = 8.45;if (str.length >= 16) {    str = @((float)value).description;}NSLog(@"%@",str);    其结果为8.45

3.改进

3.1 显然对于2.4,每次使用都这么写次判断不合适,那么,我们就可以将其抽出一个方法,但实际上这个数字的精度问题很常见,那为什么我们不将其抽成一个@(value)的分类为其写个分类方法就好了,那么,首先这个分类是什么?

NSLog(@"%@",[@(123) class]);结果为__NSCFNumber直接输入__NSCFNumber,会发现编译器报错
ps:这个是运行中的一个类,实际上是NSNumber的一个私有子纲,不过我们差不多也能猜出来,实在不行还可以google一下,很容就能找到答案

我们来验证一下:

if ([@(123) isKindOfClass:[NSNumber class]]) {    NSLog(@"Yes, I am a NSNumber.My Lord!");}结果为:Yes, I am a NSNumber.My Lord!

3.2 接着3.1,我们开始写分类方法

1.为NSNumber写个分类,实现一个方法//分类的.h声明中声明一个方法- (NSString *)dl_description;//分类的.m 实现方法- (NSString *)dl_description{    //double(CGFloat)数字转换成NSString的最高长度为16    //当其转换的长度为16时,可能遇到需求情况    if (self.description.length >= 16) {        return @([self floatValue]).description;    }    return self.description;}2.以后直接令需显示的string为@(value). dl_description即可(可别别忘记导入分类文件!)NSLog(@"%@",@(value).dl_description);

4. 问题解答

4.1可能有人会质疑,会不会有小数在double和double强制转换为float的description情况下都会出现精度问题?这里我采用以下测试,结果证实1位到4位小数没有任何问题。

[self testWithRangeNumber:10];NSLog(@"-----");[self testWithRangeNumber:100];NSLog(@"-----");[self testWithRangeNumber:1000];NSLog(@"-----");[self testWithRangeNumber:10000];//testFunc- (void)testWithRangeNumber:(int)rangeNumber{    double oneStep = 1.0 / number;    double value = 0;    for (int i = 0; i < number + 1; i++) {        value += oneStep;        NSLog(@"%@",@(value).dl_description);    }       }结果略,观察可发现一切良好

4.2 那4位小数以后呢,我这采用是以下办法

ps:不再采取步长计算是因为,0.1 * 10 ^ -n的实际数值很多就不是准确的,也是double的个近似值,在多次加的时候,偏差就会不断增大
[self test2WithLength:6];//百万分之一,小数点后6位加0.两位 为8位- (void)test2WithLength:(int)length{    int oneStep = 10;    double scale = 1;    for (int i = 0; i < length; i++) {        scale *= oneStep;        [self test2WithRangeNumber:scale andConditionNumber:length + 2]; //百万分之一,小数点后6位加0.俩位 为8位        NSLog(@"------");    }}- (void)test2WithRangeNumber:(int)rangeNumber andConditionNumber:(int)conditionNumber{    double value = 0;    for (int i = 0; i < rangeNumber + 1; i++) {//        value +=  oneStep;        value = i * 1.0 / rangeNumber;        if(@(value).dl_description.length  > conditionNumber)            NSLog(@"%@",@(value).dl_description);    }}结果为------------------------------0.0009860001------
结果证明确实有这个数,那么此时该怎么办呢?
就需要用设定精确度,如设定precision为0.000001,用deviation(偏差) = (value(即0.0009860001) - 实值(为precision的倍数))算,若if(deviation < precision),则为实值举个例子,那0.0009860001来说,可得实际值0.000986,当实值取0.000987或者0.000985时,不符合deviation < precision
但我们这里想要做,如何来弄呢?我这里有个想法,但感觉有点low,就不写了,这里也斗胆给供大家参考下吧,如有好的想法欢迎联系我~
确定精度precision,precisionLength(精度长度)value = value / precision,value = (int)value + ((value - (int)value) > 0.5); nsstring *str = value ;str = str从后往前数第precisionLength位插入一个小数点(.)for循环遍历到precisionLength,检查末尾是否为字符0,若是则删除,遇到非零结束

5. 总结

实际过程中的大多数情况下,3.2已经可以完成需求,但如果要实现小数点后6位以上依旧完成此项功能需要另想办法(到小数点后6位,仅有0.000986一个值不达标)

PS:个人总结的,难免会有疏漏,若有缺陷,欢迎指正~

0 0
原创粉丝点击