前言

相信不少人(其实我觉得应该是每个人)都遇到过一个问题,那就是当服务端返回的JSON数据中出现了小数时,客户端用CGFloat去解析时总是会出现精度丢失的问题,尤其当遇到敏感数据时,这种精度丢失是完全不能被容忍的,本文会从简单的解决方案和原理出发,一起带大家回顾一下这个其实大家以前都学过但是都忘的差不多了的小问题。

如何解决浮点型精度问题

四舍五入处理

//例如服务端返回了这么一个json
{
    "price":1.9
}

// 客户端解析price后并且打印1.9
CGFloat price = model.price
NSLog(@"%f",price)
得到的结果是 1.8999999999999999

这个时候呢,作为一个咸鱼开发者,可以写下如下代码:

NSLog(@"%.2f",price)
输出:1.89
//不够准确,没关系,还有办法
NSLog(@"f%@",round(price*10)/10);
输出1.9

当然了,还有apple专门为精度问题提供的 NSDecimalNumber类型也可以解决这个问题。NSDecimalNumber的用法非常简单。(至于怎么个简单,请自行百度,别问我,我不百度也写不出来)

更优的解决方案

  • 那么问题来了,作为一个懒到一整年都不愿意写文章的咸鱼中的咸鱼,我应该选择哪种方式去解决这个问题呢?

好的,重头戏来了,接下来,我们上代码!

//服务端必须返回字符串类型,如果服务端没有照做: **请带着锤子和煤气罐去找后端开发人员解决**
@property (nonatomic,copy) NSString *price;

是的,我会选择不解决这个问题,把皮球踢出去,这才是一个合格的开发者该做的事情嘛!

精度丢失的原因

解决浮点精度问题是一个方面,但是如此简单的内容不足以我水完一整篇文章,那么接下来,我们来讲讲为什么好好的数字解析出来,他咋就不准确了呢?

可能很多人都能大概讲出来精度丢失是因为浮点数存储方式的问题,毕竟这玩意儿其实专业对口的筒子们在校的时候都学过,但是大家摸摸自己的小脑袋,嘿,是不是和我一样?全忘光了?

而且鉴于总有些基础很牛逼的面试官和刚好复习过这部分内容的装逼面试官就是喜欢挑这些小问题来刁难咱们这些老年摸鱼程序员,下面我们就来复习一下这部分的知识吧。

浮点类型的存储方式

浮点类型在计算机中的存储方式是以科学计数法的方式来存储的:

例如: 科学计数法表示小数
90.9 => 9.09 x 10^1
8.3  => 8.3 x 10^0

我们以8.3为例子,要存储8.3 (8.3 x 10^0), 首先肯定要将8.3转化为2进制,8转为二进制时1000,那么0.3呢?

年迈的程序员哟,你是不是猛然发现居然忘了小数是如何转化为2进制的呀,放下你准备百度的颤抖的双手,你想要的,我这里都有!

以0.9为例子,将0.9乘以2,得到的数字整数部分为二进制的小数第一位,将结果部分的小数部分取出并再乘以2,取整数部分为第2位,不断重复以上操作,直到结果等于0或者出现循环为止。

0.9 *2 = 1.8  第1位 1
0.8 *2 = 1.6  第2位 1
0.6 *2 = 1.2  第3位 1
0.2 *2 = 0.4  第4位 0
0.4 *2 = 0.8  第5位 0
0.8 *2 = 1.6  第6位 1 出现循环了

那么0.9的二进制就是 0.1 1100 1100 1100 1100... 其中1100无限循环

经过上面一番复习我们知道8.3的二进制可表示为 1000.0 1001 1001 1001... (1001无限循环) 用科学计数法表示则为, 1.000 0 1001 1001 1001 x 2^3

整数部分 指数部分 小数部分
1 3 .000 0 1001 1001 1001

而我们知道float是4(32位)个字节,在存储时他的每个bit是如下分配的

第31位符号位 23-30位(指数位) 0-22位(小数位)
1 3 .000 0 1001 1001 1001

有效位数

可以看到尾数部分有23位,但是由于是科学技术法之后任何一个数字都可以用  1.aaa x 2^b 这种形式来表示,所以1可以省略,(这个时候就有人要抬杠了,为啥,那遇到0.aaa x 2^b这种咋办呀! 答曰:b可以是负数啊),那么总共23位的数据实际可以表达的位数其实就是24位。

  • 24位可以表达的最大数字是 16777215 ,如果大于这个数就无法精确表示了。

当然大于16777215一不定是完全不能精确表示的,比如16777216,他的二进制表达形式是1后面带1堆0. 因为他是2的整数次幂,那么后面全是0 所以23位能把该数字存储下来,如果在23位0后面还出现了1就不行了,以此类推,16777215 后面还会有数字刚好可以满足这种2的整数次幂的条件,也可以正确表达。

虽然大于16777215的数字也有部分可以精确表达,但是我们谈精度的话肯定就要精确了,那么能精确表达的数字就只有0-16777215之间的了,16777215之间我们数一下,一共是8位,但是由于最高位1开头并不能全部包含,所以说精度应该是7位有效数子。

另外提一下浮点数的表达范围,这个范围肯定是由指数来确定了,具体是多少我就不算了,大家有兴趣的可以去算一算。

指数的存储方式:移位存储

可以看到指数部分一共是给了8个bit位,由于指数有正负,那么假设我们第一位表示符号位,那么我们可以表示的数字范围为 -127 ~ 127

那么能表示的范围被分为两部分:

  • 1 000000 0~ 1 111111 1 => -0 到 -127

  • 0 000000 0~ 0 111111 1 => 0 到 127

很明显,如果这样会出现一个 -0 和一个 0,为了避免这个问题所以出现了移位存储:即如果最高位不用来表示符号位,8个bit 可以存储的范围是 0-255,我们重新来规划下两个区间:

  • 00000000 - 01111111 => 0-127 减去127则表示-127-0之前的数字
  • 10000000 - 11111111 => 128-256 减去127则表示1-127之前的数字

这样一来规避了 -0的问题, 上文中所说的8.3 转化为小数后的指数位是3,那么3实际存储的时130,也就是 10000010,我们更新一下实际存储的表格

第31位符号位 23-30位(指数位) 0-22位(小数位)
1 10000010 .000 0 1001 1001 1001

double类型

double类型是8字节64位,其表示的位数和float所不同只有位数差别,其他都一样,双精度表示的位数如下:

第63位符号位 52-62位(指数位) 0-51位(小数位)

总结:输出结果丢失精度原因

回到最初的1.9这个数字打印为1.8999999999999999,现在我们应该知道为什么了,因为1.9转2进制时是个无限循环,由于存储的原因后面的循环部分被只被截取了23位,还原回来的十进制数字和原先肯定就不一样了。 同理,如果小数点后面是.5这种5的倍数的小数,打印出来的小数一般都会是正常的,因为.5转2进制时并不是无限循环的。

到此这篇关于iOS浮点类型精度问题的原因与解决办法的文章就介绍到这了,更多相关iOS浮点类型精度问题内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

iOS浮点类型精度问题的原因与解决办法的更多相关文章

  1. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  2. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  4. ios – Testflight无法安装应用程序

    我有几个测试人员注册了testflight并连接了他们的设备……他们有不同的ios型号……但是所有这些都有同样的问题.当他们从“safari”或“testflight”应用程序本身单击应用程序的安装按钮时……达到约90%并出现错误消息…

  5. ibm-mobilefirst – 在iOS 7.1上获取“无法安装应用程序,因为证书无效”错误

    当我的客户端将他们的设备更新到iOS7.1,然后尝试从AppCenter更新我们的应用程序时,我收到了上述错误.经过一番搜索,我找到了一个类似问题的帖子here.但是后来因为我在客户端使用AppCenter更新应用程序的环境中,我无法使用USB插件并为他们安装应用程序.在发布支持之前,是否有通过AppCenter进行下载的解决方法?

  6. ios – 视图的简单拖放?

    我正在学习iOS,但我找不到如何向UIView添加拖放行为.我试过了:它说“UIView没有可见的接口声明选择器addTarget”此外,我尝试添加平移手势识别器,但不确定这是否是我需要的它被称为,但不知道如何获得事件的坐标.在iOS中注册移动事件回调/拖放操作的标准简单方法是什么?

  7. ios – 什么控制iTunes中iPhone应用程序支持的语言列表?

    什么控制iPhone应用程序的iTunes页面中支持的语言?

  8. ios – 获得APNs响应BadDeviceToken或Unregistered的可能原因是什么?

    我知道设备令牌在某些时候是有效的.用户如何使其设备令牌变坏?从关于“未注册”的文档:Thedevicetokenisinactiveforthespecifiedtopic.这是否意味着应用程序已被删除?.您应该看到四种分发方法:如果您选择AppStore或Enterprise,您将在后面的对话框中看到Xcode将APNS权利更改为生产:如果选择AdHoc或Development,则aps-environment下的文本将是开发,然后应与后端的配置匹配.

  9. ios – 当我关闭应用程序时,我从调试器获得消息:由于信号15而终止

    我怎么能解决这个问题,我不知道这个链接MypreviousproblemaboutCoredata对我的问题有影响吗?当我cmd应用程序的Q时,将出现此消息.Messagefromdebugger:Terminatedduetosignal15如果谁知道我以前的问题的解决方案,请告诉我.解决方法>来自调试器的消息:每当用户通过CMD-Q(退出)或STOP手动终止应用程序(无论是在iOS模拟器中还是

  10. ios – NSUbiquityIdentityDidChangeNotification和SIGKILL

    当应用程序被发送到后台时,我们会删除观察者吗?我遇到的问题是,当UbiquityToken发生变化时,应用程序终止,因为用户已经更改了iCloud设置.你们如何设法订阅这个通知,如果你不这样做,你会做什么来跟踪当前登录的iCloud用户?

随机推荐

  1. iOS实现拖拽View跟随手指浮动效果

    这篇文章主要为大家详细介绍了iOS实现拖拽View跟随手指浮动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  2. iOS – genstrings:无法连接到输出目录en.lproj

    使用我桌面上的项目文件夹,我启动终端输入:cd然后将我的项目文件夹拖到终端,它给了我路径.然后我将这行代码粘贴到终端中找.-name*.m|xargsgenstrings-oen.lproj我在终端中收到此错误消息:genstrings:无法连接到输出目录en.lproj它多次打印这行,然后说我的项目是一个目录的路径?没有.strings文件.对我做错了什么的想法?

  3. iOS 7 UIButtonBarItem图像没有色调

    如何确保按钮图标采用全局色调?解决方法只是想将其转换为根注释,以便为“回答”复选标记提供更好的上下文,并提供更好的格式.我能想出这个!

  4. ios – 在自定义相机层的AVFoundation中自动对焦和自动曝光

    为AVFoundation定制图层相机创建精确的自动对焦和曝光的最佳方法是什么?

  5. ios – Xcode找不到Alamofire,错误:没有这样的模块’Alamofire’

    我正在尝试按照github(https://github.com/Alamofire/Alamofire#cocoapods)指令将Alamofire包含在我的Swift项目中.我创建了一个新项目,导航到项目目录并运行此命令sudogeminstallcocoapods.然后我面临以下错误:搜索后我设法通过运行此命令安装cocoapodssudogeminstall-n/usr/local/bin

  6. ios – 在没有iPhone6s或更新的情况下测试ARKit

    我在决定下载Xcode9之前.我想玩新的框架–ARKit.我知道要用ARKit运行app我需要一个带有A9芯片或更新版本的设备.不幸的是我有一个较旧的.我的问题是已经下载了新Xcode的人.在我的情况下有可能运行ARKit应用程序吗?那个或其他任何模拟器?任何想法或我将不得不购买新设备?解决方法任何iOS11设备都可以使用ARKit,但是具有高质量AR体验的全球跟踪功能需要使用A9或更高版本处理器的设备.使用iOS11测试版更新您的设备是必要的.

  7. 将iOS应用移植到Android

    我们制作了一个具有2000个目标c类的退出大型iOS应用程序.我想知道有一个最佳实践指南将其移植到Android?此外,由于我们的应用程序大量使用UINavigation和UIView控制器,我想知道在Android上有类似的模型和实现.谢谢到目前为止,guenter解决方法老实说,我认为你正在计划的只是制作难以维护的糟糕代码.我意识到这听起来像很多工作,但从长远来看它会更容易,我只是将应用程序的概念“移植”到android并从头开始编写.

  8. ios – 在Swift中覆盖Objective C类方法

    我是Swift的初学者,我正在尝试在Swift项目中使用JSONModel.我想从JSONModel覆盖方法keyMapper,但我没有找到如何覆盖模型类中的Objective-C类方法.该方法的签名是:我怎样才能做到这一点?解决方法您可以像覆盖实例方法一样执行此操作,但使用class关键字除外:

  9. ios – 在WKWebView中获取链接URL

    我想在WKWebView中获取tapped链接的url.链接采用自定义格式,可触发应用中的某些操作.例如HTTP://我的网站/帮助#深层链接对讲.我这样使用KVO:这在第一次点击链接时效果很好.但是,如果我连续两次点击相同的链接,它将不报告链接点击.是否有解决方法来解决这个问题,以便我可以检测每个点击并获取链接?任何关于这个的指针都会很棒!解决方法像这样更改addobserver在observeValue函数中,您可以获得两个值

  10. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

返回
顶部