iOS浮点类型精度问题的原因与解决办法

目录
  • 前言
  • 如何解决浮点型精度问题
    • 四舍五入处理
    • 更优的解决方案
  • 精度丢失的原因
    • 浮点类型的存储方式
    • 有效位数
    • 指数的存储方式:移位存储
    • double类型
  • 总结:输出结果丢失精度原因

前言

相信不少人(其实我觉得应该是每个人)都遇到过一个问题,那就是当服务端返回的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浮点类型精度问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解iOS之关于double/float数据计算精度问题

    1.我们的app有一个判断,当用户输入的值,小于等于剩余的余额时,给用户弹窗,代码不往下执行 当用户输入 0.01 时,po 一下的结果是:0.10000.... ,当用户的余额是0.01时,po一下网络请求的返回结果是:0.009999... 当用户输入 66.05时,po  一下的结果是:66.049999...,当用户的余额是66.05时,po一下网络请求的返回结果是:66.049999... 所以,当数据较大时,不会有影响,因为用户输入的数据和网络返回数据都被变成了不精确的小值了:但是,

  • iOS浮点类型精度问题的原因与解决办法

    目录 前言 如何解决浮点型精度问题 四舍五入处理 更优的解决方案 精度丢失的原因 浮点类型的存储方式 有效位数 指数的存储方式:移位存储 double类型 总结:输出结果丢失精度原因 前言 相信不少人(其实我觉得应该是每个人)都遇到过一个问题,那就是当服务端返回的JSON数据中出现了小数时,客户端用CGFloat去解析时总是会出现精度丢失的问题,尤其当遇到敏感数据时,这种精度丢失是完全不能被容忍的,本文会从简单的解决方案和原理出发,一起带大家回顾一下这个其实大家以前都学过但是都忘的差不多了的小问

  • Mysql/MariaDB启动时处于进度条状态导致启动失败的原因及解决办法

    今天打开网站突然发现网站无法打开,后来通过SSH登陆服务器发现MARIADB数据库没有启动成功,再次启动还是无法成功启动,一直处于启动进度条,进度条结束后提示ERROR.查看日志出现以下错误: InnoDB: Unable to lock ./ibdata1, error: 11 后经调试发现是因为MariaDB数据库所在分区已经满了,造成无法启动. 只有将MariaDB数据库存放数据目录移动到另外一个磁盘份额比较大的分区或者将当前分配删除一些不必要的文件. 移动办法: 1.停掉mysql服务器

  • Oracle用户被锁的原因及解决办法

    在登陆时被告知test用户被锁 1.用dba角色的用户登陆,进行解锁,先设置具体时间格式,以便查看具体时间 SQL> alter session set nls_date_format='yyyy-mm-dd hh24:mi:ss'; Session altered. 2.查看具体的被锁时间 SQL> select username,lock_date from dba_users where username='TEST'; USERNAME LOCK_DATE TEST 2009-03-1

  • BootStrap.css 在手机端滑动时右侧出现空白的原因及解决办法

    最近的一个项目 前台使用了 bootstrap.css + angularjs, 后台只处理数据(用的php,处理结果直接 json_encode($arr),非常爽).一直在Chrome的仿真机测试非常完美, 没有进行真机测试.完成后,到手机测试时傻了,左右滑动页面时,竟然出现了一个 空白的竖条(如下图所示).判断是margin-right 设置的长度所致,检查css,并没有相关代码.看来问题出现在了 bootstrap .虽然不影响 程序的使用,但是感觉非常别扭,一定要修复它. 检查页面,发

  • Android开发中requestfocus()无效的原因及解决办法

    前言 最近做公司项目的时候,经常会遇到一个问题,就是我为某个控件如EditText设置requestfocus()的时候不管用,比如说登陆的时候,我判断下用户输入的密码,如果正确就登陆,错误就提示密码错误,并且输入框获取焦点,但是实际中确不起作用 package com.example.hfs.requestfocusdemo; import android.content.Intent; import android.support.v7.app.AppCompatActivity; impo

  • MySQL ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) 的原因分解决办法

    MySQL ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) 的解决办法和原因 这两天下载了MySQL5.7.11进行安装,发现到了初次使用输入密码的时候,不管怎样都进不去,即使按照网上说的在mysqld 下面添加skip-grant-tables也是不行,后来研究了两天,终于找出原因和解决办法. 复制代码 代码如下: [mysqlld] skip-grant-tables: 原因

  • PHP验证码无法显示的原因及解决办法

    PHP验证码无法显示的原因及解决办法 一.如果是utf-8,就有可能是BOM没有清除 二.在Header("Content-type: image/PNG"); 之前有输出 三.第一行PHP隐藏了代码,如空格,回车等. 解决代码: $image_width=70; //设置图像宽度 $image_height=18; //设置图像高度 $new_number=$_GET[num]; //$new_number=5; $num_image=imagecreate($image_width

  • 基于Bootstrap实现的下拉菜单手机端不能选择菜单项的原因附解决办法

    基于Bootstrap做的下拉菜单在电脑浏览器中可正常使用,在手机浏览器中能弹出下拉列表,却不能选择列表中的菜单项,通过自己百度查找原因将bootstrap脚本文件中的ontouchstart 替换为 disable-ontouchstart可以解决,替换后并不能解决.(红米手机UC浏览器不支持,小米手机UC浏览器正常,其他暂时未测试) jquery:v1.11.2 bootstrap:v3.3.4 以下为前台页面代码: <div class="input-group">

  • 当master down掉后,pt-heartbeat不断重试会导致内存缓慢增长的原因及解决办法

    最近同事反映,在使用pt-heartbeat监控主从复制延迟的过程中,如果master down掉了,则pt-heartbeat则会连接失败,但会不断重试. 重试本无可厚非,毕竟从使用者的角度来说,希望pt-heartbeat能不断重试,直到重新连接上数据库.但是,他们发现,不断的重试会带来内存的缓慢增长. 重现 环境: pt-heartbeat v2.2.19,MySQL社区版 v5.6.31,Perl v5.10.1,RHEL 6.7,内存500M 为了避免数据库启停对pt-heartbea

  • 微信小程序中hidden不生效原因的解决办法

    微信小程序中hidden不生效原因的解决办法 例如如下布局: <view hidden="true" style="display:flex;flex-direction: row;"> <text>text1</text> <text>text2</text> </view> 你会发现hidden没生效.经我实验发现hidden元素对块状布局才生效,所以这段代码里导致hidden没生效的罪魁祸

随机推荐