Float的基本介绍

关于浮点数,为何它生来就可能存在偏差?带着好奇查阅了一些介绍,而后作了简单汇总。这只是一段知识的开始,后续还会继续完善。(很难过,这里的MarkDown不支持编辑公式。请移驾本人的博客地址: http://neojos.com )

—— 荡荡上帝,下民之辟。疾威上帝,其命多辟。天生烝民,其命匪谌。靡不有初,鲜克有终。git

Floating-point represent

浮点数在计算机中是如何表示的?由于它有小数点,那么小数点后面的数,该如何用二进制来表示呢?咱们都知道,浮点数自己就存在偏差,在工做中,你所使用的float都是一个近似数。这又是什么缘由致使的呢?golang

1. Fixed-point

fixed-point 是将bit位拆分红固定的两部分:小数点前的部分和小数点后的部分。拿32 bitfixed-point表示举例,能够将其中的24 bit用于表示整数部分,剩余的8 bit表示小数部分。less

假如要表示1.625,咱们能够将小数点后面的第一个bit表示$\frac12$,第二个bit表示1/4,第三个1/8一直到最后一个1/256。最后的表示就是00000000 00000000 00000001 10100000。这样其实也好理解,由于小数点前是从$2^0$开始向左成倍递增,小数点后从$2^{-1}$开始向右递减。socket

由于小数点后面的部分始终小于1,上面这种表达方式能表达的最大数是255/256。再比这个数小,这种结构就没法表示了。函数

Floating-point basics

根据上面的思路,咱们用二进制表达一下5.5这个十进制数,转化后是$101.1_{(2)}$。继续转换成二进制科学计数法的形式:$1.011_{(2)} * 2^2$。在转换的二进制科学计数法过程当中,咱们将小数点向左移了2位。就跟转换十进制的效果同样:$101.1_{(10)}$ 的科学计数形式为$1.011 * 10^2$oop

对于二进制科学计数法表达的5.5,咱们将其拆分红2部分,1.011是一部分,咱们称为mantissa。指数2是另外一部分,称为exponent。下面咱们要将$1.011_{(2)} * 2^2$ 映射到计算机存储的8 bit结构上。spa

咱们用第一个bit来表示正负符号,1表示负数,0表示正数。紧接着的4 bit用来表示exponent + 7后的值。 4 bit最大能够表示到15,这也就意味着当前的exponent不能超过8,不能低于-7。最后的3 bit用于存储mantissa的小数部分。你可能有疑问,它的整数部分怎么办呢?这里咱们约定整数部分都调整成1,这样就能够节省1 bit了。举个例子,若是要表示的十进制数是0.5,那么最后的二进制数不是$0.1_{(2)}$,而是$1.0 * 2^{-1}$。最后表示的结果就是:0 1001 011code

再来一个decode的例子,即将0 0101 100还原回原始值。根据以前的描述0101表示的十进制是5,因此exponent = -2,表示回二进制科学计数法的结果:$1.100 * 2^{-2} = 0.011_{(2)}$。咱们继续转换成真实精度的数:0.375orm

最后能够看在,若是mantissa的长度超过3 bit表示的范围,那么数据的存储就会丢失精度,结果就是一个近似值了。blog

1. Representable numbers

继续按照上面的思路,如今8 bit的浮点表示能表示的数值区间更大。

要表示最小正数的话,sign置为0,接下来的4 bits置为0000,最后的mantissa也置为000。那么最终的表示结果就是:$1.000_{(2)} * 2^{-7} = 2^{-7} ≈ 0.0079_{(10)}$

表示最大正数的话,sign置为0,其余位也都置为1。最终表示的结果:$1.111_{(2)} * 2^{8} = 111100000_{(2)} = 480_{(10)}$。因此8 bits浮点表示的正数范围(0.0079, 480]。而8 bits 二进制表示的范围是[1, 127]。范围确实大了不少。

可是必须注意:浮点数没法准确表示该区间内的全部数。拿十进制51来讲,用二进制表示是110011。转化为8 bits的浮点数表示:$110011_{(2)} = 1.10011_{(2)}*2^{5}$。当咱们试着去存储的时候,发现3 bitsmantissa放不下如今的10011。咱们不得不作近似取值,将结果修改成$1.101_{(2)} * 2^{5} = 110100_{(2)} = 52_{(10)}$。因此,在咱们8 bits 表达的浮点数中51 = 52。这样的处理有时候让咱们很无奈,但这也是为了让8 bits表示更大范围的数所必须付出的代价。

从上面的过程当中,咱们还能够理解在计算中round upround down的策略。当小数点后的数超过3 bit时,就是展示这个策略的时候。拿19举例,表示成二进制科学计数法:$1.0011 * 2^4$。若是执行round up,最终的结果就是$1.010_{(2)} * 2^4 = 20_{(10)}$。若是执行round down,结果即是$1.001_{(2)} * 2^4 = 18$

若是咱们要提升浮点数表达的精度,mantissa区间就须要更多的bit来表示。拿float32来举例,它是1 bitsign8 bitsexponent以及23 bits表示的mantissa

IEEE standard

该标准定义了更长的bit来提升表达的精度和范围。

1. IEEE formats

它定义了上面描述的signexponentmantissa以及excess(就是8 bits表示过程当中用到的7)。

sign exponent mantissa exponent significant
format bit bits bits excess digits
Our 8-bit 1 4 3 7 1
Our 16-bit 1 6 9 31 3
IEEE 32-bit 1 8 23 127 6
IEEE 64-bit 1 11 52 1,023 15
IEEE 128-bit 1 15 112 16,383 34

2. 非数值

顾名思义:not a number,程序中偶尔会看到的NaN。好比0/0∞ + −∞等。这类数值在表示中exponent都是1。

3. 运算

讨论 x + (y + z)(x + y) +z的结果是否相同,拿上面8 bits的浮点数表示来讲明。其中x=1y=52z= -52。咱们注意到y+z = 0,因此第一个计算结果是1。但(x+y)的结果仍然是52,这主要是由于mantissa没法表示,致使最终结果取近似值仍是52,最终结果是0。

另一个例子:1/6 + 1/6 + 1/6 + 1/6 + 1/6 + 1/6 = 1等式也是不存在的。在8 bits的表示中没法准确的表示1/6,因此最终的结果要比1小。

在程序开发过程当中,咱们必须意识到这类问题产生的影响。

float to float

Round返回最近的整数,但返回值是一个float64类型。返回值是四舍五入后的结果。

a := math.Round(12.3456)
//output: 12

相对应的函数,还有FloorCeil

// Floor returns the greatest integer value less than or equal to x.
// output: 12
a := math.Floor(12.3456)

// Ceil returns the least integer value greater than or equal to x.
// output: 13
a := math.Ceil(12.3456)

match/big

关于浮点数的比较:

// change these value and play around
float1 := 123.4568
float2 := 123.45678

// convert float to type math/big.Float
var bigFloat1 = big.NewFloat(float1)
var bigFloat2 = big.NewFloat(float2)

// compare bigFloat1 to bigFloat2
result := bigFloat1.Cmp(bigFloat2)

参考文章:

  1. Golang : Compare floating-point numbers
  2. Floating-point representation
  3. Floating Point Numbers
相关文章
相关标签/搜索