浮点类型是如何存储的

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浮点类型是如何存储的相关的知识,希望对你有一定的参考价值。

参考技术A

计算机中最小的存储单位是bit只能保存0和1,整数在内存中如何存储我们都知道,将要存储的数字转成2进制即可

用windows自带的计数器可以方便的查看整数对应的2进制值
如:
byte类型(单字节)

那浮点类型是如何用这么少的字节(如float 4字节)表示这么大(float 最大 3.4028235E38)的数字呢?

浮点数,是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。

科学计数法是一种记数的方法。把一个数表示成a与10的n次幂相乘的形式(1≤|a|<10,a不为分数形式,n为整数),这种记数法叫做科学计数法。当我们要标记或运算某个较大或较小且位数较多时,用科学计数法免去浪费很多空间和时间。

这也是一种目前最常用的浮点数标准!为许多CPU与浮点运算器所采用。

简单的说就是将一个浮点数字拆成3个部分(符号部分、指数部分、小数部分) 存储在连续的bit中,类似科学计数法。

用 S,E,M来表示一个数 V 的,即 V =(-1)S × M × 2E ,如下:

其中:

其中d.dd...d 为有效数字,β为基数,e 为指数

有效数字中 数字的个数 称为 精度 ,我们可以用 p 来表示,即可称为 p 位有效数字精度。
每个数字 d 介于 0 和基数 β 之间,包括 0。

对十进制的浮点数,即基数 β 等于 10 的浮点数而言,上面的表达式非常容易理解。
如 12.34,我们可以根据上面的表达式表达为:
1×10 1 + 2×10 0 + 3×10 -1 + 4×10 -2
其规范的浮点数表达为: 1.234×10 1

但对二进制来说,上面的表达式同样可以简单地表达。
唯一不同之处在于:二进制的 β 等于 2,而每个数字 d 只能在 0 和 1 之间取值。

如二进制数 1001.101 ,我们可以根据上面的表达式表达为:
1×2 3 + 0×2 2 + 0×2 1 + 1×2 0 + 1×2 -1 + 0×2 -2 + 1×2 -3
其规范浮点数表达为: 1.001101×2 3

二进制数 1001.101 转成十进制如下:

由上面的等式,我们可以得出:
向左移动二进制小数点一位相当于这个数除以 2,而向右移动二进制小数点一位相当于这个数乘以 2。
如 101.11 = 5又3/4 (5.75),向左移动一位,得到 10.111 = 2又7/8 (2.875)。

除此之外,我们还可以得到这样一个基本规律:
一个十进制小数要能用浮点数精确地表示,最后一位必须是 5(当然这是必要条件,并非充分条件)。
如下面的示例所示:

基本换算方法:
将10进制的数拆分成整数和小数两个部分
整数部分除以2,取余数;小数部分乘以2,取整数位。

示例:
将十进制 1.1 转成 二进制

整数部分:1
1

小数部分:0.1

二进制形式表示为:
1.000110011001100110011...

再加上整数1,约等于:
1.099609375

计算的位数越多越精确

注意:
二进制小数不像整数一样,只要位数足够,它就可以表示所有整数。
在有限长度的编码中,二进制小数一般无法精确的表示任意小数,比如十进制小数0.2,我们并不能将其准确的表示为一个二进制数,只能增加二进制长度提高表示的精度。

根据 IEEE 754 浮点“双精度格式”位布局。

如果参数是正无穷大,则结果为 0x7ff0000000000000L。
如果参数是负无穷大,则结果为 0xfff0000000000000L。
如果参数是 NaN,则结果为 0x7ff8000000000000L。

根据 IEEE 754 浮点“单一格式”位布局。

如果参数为正无穷大,则结果为 0x7f800000。
如果参数为负无穷大,则结果为 0xff800000。
如果参数为 NaN,则结果为 0x7fc00000。

这里以 double类型说明

将一个浮点数与上面的掩码进行与运算,即可得到对应的 符号位、指数位、尾数位 的值。

1.000110011001100110011...

所以存为:
0 01111111111 000110011001100110011...

根据 IEEE 754 规范

在二进制,第一个有效数字必定是“1”,因此这个“1”并不会存储。
单精和双精浮点数的有效数字分别是有存储的23和52个位,加上最左边没有存储的第1个位,即是24和53个位。

通过计算其能表示的最大值,换十进制来看其精度:

浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。而往往产生误差不是因为数的大小,而是因为数的精度。

我自己理解为分两种情况(这个不一定是对)

通过上面的转换示例,我们知道小数的二进制表示一般都不是精确的,在有限的精度下只能尽量的表示近似值

值本身就不是精确的,再进行计算就很可能产生误差

输出:

0.1
原始值: 0 01111111011 1001100110011001100110011001100110011001100110011010
指数:1019 -1023 = -4
二进制形式:
0.00011001100110011001100110011001100110011001100110011010

0.2
原始值:0 01111111100 1001100110011001100110011001100110011001100110011010
指数:1020 -1023 = -3
二进制形式:
0.001001100110011001100110011001100110011001100110011010

0.3
原始值:0 01111111101 0011001100110011001100110011001100110011001100110100
指数:1021 = -2
二进制形式:
0.010011001100110011001100110011001100110011001100110100

二进制加法运算

这里用float验证,float最大的精度是8位数

对于不能精确的表示的数,采取一种系统的方法:找到“最接近”的匹配值,它可以用期望的浮点形式表现出来,这就是舍入。

对于舍入,可以有很多种规则,可以向上舍入,向下舍入,向偶数舍入。如果我们只采用前两种中的一种,就会造成平均数过大或者过小,实际上这时候就是引入了统计偏差。如果是采用偶数舍入,则有一半的机会是向上舍入,一半的机会是向下舍入,这样子可以避免统计偏差。而 IEEE 754 就是采用向最近偶数舍入(round to nearest even)的规则。

(这段是网上抄的)

这里以java语言示例,用大端的方式示例(网络序)

java中是以大端模式存储的,java对我们屏蔽了内部字节顺序的问题以实现跨平台!

实际在不同的cpu架构下,存储方式不同,我们常用的X86是以小端的模式存储的。

网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。

输出:

Elasticsearch:如何将浮点值存储到整型字段中

在我之前的文章 “Elasticsearch:Elasticsearch 中的数据强制匹配” 中,我详述了如何使用 coerce 属性来强制匹配一个不同的数据类型。在今天的文章中,我们来展示 Elasticsearch 是如何把一个浮点数保存到一个不同的数据类型中,比如整型数据类型。

首先,我们来创建一个如下的索引:

PUT my_index/_doc/1
{
  "score": 1
}

我们接下来使用如下的方法来得到上述索引 my_index 里字段的数据类型:

GET my_index/_mapping

上面的命令返回的结果为:

{
  "my_index" : {
    "mappings" : {
      "properties" : {
        "score" : {
          "type" : "long"
        }
      }
    }
  }
}

我们可以看到经过 dynamic mapping,索引中的 score 字段自动被设置为 long 数据类型。

接下来,我们按照如下的方式来摄入更多的数据:

PUT my_index/_doc/2
{
  "score" : 2
}

PUT my_index/_doc/3
{
  "score" : "3"
}

PUT my_index/_doc/4
{
  "score" : 4.5
}

PUT my_index/_doc/5
{
  "score" : "5.1"
}

在上面的命令中,我们发现有些数据不是整型数值,比如 "3", "4.5" 及 “5.1”。那么它们到底是否能被正确无误地写入文档呢?我们分别执行上面的每一个指令,我们发现它们的结果没有任何一个是返回错误的:

{
  "_index" : "my_index",
  "_type" : "_doc",
  "_id" : "5",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 8,
  "_primary_term" : 1
}

这充分地说明了它们的写入是没有任何问题的。如果你对这个还不是特别能够理解,请阅读我之前的文章 “Elasticsearch:Elasticsearch 中的数据强制匹配”。这是因为在默认的情况下, coerce 是 true。它会自动帮我们把数据转换为我们索引所定义的数据类型。也就是说, "3" 字符串会被转换为整数 3 而写入文档内部。那么有人可能会想到查询一下刚才写入的文档:

GET my_index/_search
    "hits" : [
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "score" : 1
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "score" : 2
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "score" : "3"
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "score" : 4.5
        }
      },
      {
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 1.0,
        "_source" : {
          "score" : "5.1"
        }
      }
    ]

上面的结果显示它们和我们之前写入时时完全一模一样的。并没有任何的变化。它们并没有显示为整型值。那么这到底是咋样一回事呢?

_source 只是一种错觉

这个问题的谜底的最后一部分是 Elasticsearch 从不更改 _source。 但是存储的字段 score 与你期望的一样,它是 long 类型的字段。 你可以通过在字段上运行聚合来验证这一点:

GET my_index/_search
{
  "size": 0,
  "aggs": {
    "my_sum": {
      "sum": {
        "field": "score"
      }
    }
  }
}

上面的命令显示的结果是:

{
  "took" : 12,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 5,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "my_sum" : {
      "value" : 15.0
    }
  }
}

从上面我们可以看出来 my_sum 为 15。它是 1 + 2 + 3 + 4 + 5 = 15,也就是 5 个文档的 long 类型值的总和。

结论

我希望你不像以前那样困惑,或者至少喜欢这个谜题。 作为最后的说明,请注意 coerce 可能在未来被删除,因为它是一个陷阱功能 - 特别是在截断浮点数方面。

以上是关于浮点类型是如何存储的的主要内容,如果未能解决你的问题,请参考以下文章

Elasticsearch:如何将浮点值存储到整型字段中

Elasticsearch:如何将浮点值存储到整型字段中

请问浮点型数据在计算机是怎么存储的

C/C++中如何判断浮点类型相等?

什么是浮点(float)数据类型?

C语言学习 -- 整型与浮点型在内存中的存储