当前位置: 首页 > news >正文

十进制浮点数的表示方法

使用十进制浮点数,可以避免二进制浮点数与我们习惯的十进制数之间的表示误差.这个在金融领域是非常重要的.但是计算机基本都只能对二进制浮点数进行计算,也就是IEEE754格式表示的浮点数.很多程序都会自己模拟十进制浮点数的计算.为了统一,IEEE754做了扩展,包括了十进制的浮点数.

IEEE 754-2008里面规定了十进制浮点数的一些规范.不过里面没有说具体的二进制表示方法.只是规定了32位,64位,128位的十进制浮点数的表示范围和有效位数. 因为具体一个浮点数的二进制里面每个位表示啥,都是每个机器自己决定的.不需要跟外界一致.只是在传输的时候要保证数据的精度和范围一致就行了.下表来自wikipedia,列出了每种浮点数的有效位数,指数的范围.

Name Common name Base Digits E min E max
binary16 Half precision 210+1 -1415
binary32 Single precision 223+1 -126127
binary64 Double precision 252+1 -10221023
binary128 Quadruple precision 2112+1 -1638216383
decimal32 107-9596
decimal64 1016-383384
decimal128 1034-6143

6144

可见十进制浮点数的有效位数和指数的范围,都比二进制浮点的小. 这是因为用二进制表示十进制数,会有些麻烦,为了计算处理更方便,就牺牲了一些表示范围.

实际的系统中,十进制浮点数有两种表示方法,分别是Densely Packed Decimal(密集十进制数)和Binary Integer Decimal(二进制整数表示的十进制数).

DPD表示方便转换成十进制的浮点数字符串,但是需要专门的计算单元来做计算,软件模拟比较麻烦.

而BID表示更直观,转换到二进制会比较容易.很方便用二进制的整数运算单元来计算.

所以Power6上有了硬件的十进制浮点计算单元,就用DPD表示.而在x86 x64 cpu上没有十进制计算单元, 各种软件实现的十进制浮点库默认大都用BID方式表示.比如Intel就实现了一个开源的c 语言的十进制浮点数库。http://software.intel.com/en-us/articles/intel-decimal-floating-point-math-library/

十进制浮点的意义,在于更符合人们的习惯,比如下面的例子

#include<stdio.h>
int main()
{
double a = 7.0;
double b = 0.00007;
printf("%d/n",a==b*100000);
}
正确的输出应该是1,但是实际的输出结果是0,在做相等比较的时候,还不得不考虑一下这个误差了。而某些时候误差会在计算过程中累计,变成比较明显的错误了。

如果用intel的十进制浮点库赖做这个计算,结果就会不同了。intel这个库明显还在试验阶段,用起来比较麻烦。

int main()
{
Decimal64 a, b, c;
_IDEC_round my_rnd_mode = _IDEC_dflround;
_IDEC_flags my_fpsf = _IDEC_allflagsclear;
a = bid64_from_int32(7);
b = bid64_from_string("0.00007",my_rnd_mode, &my_fpsf);
c = bid64_mul(b,bid64_from_int32(100000),my_rnd_mode,&my_fpsf);
printf("%d/n",bid64_quiet_equal(a,c,&my_fpsf));
return 0;
}

使用和double位数相同的Decimal64,结果就是1了。这里显然不是精度的问题,而是十进制浮点数能丝毫不变的表示十进制的小数。

我们可以看到这里使用的是BID的表示方法。函数名前面都带个bid前缀。

接下来,我们来具体看看BID的表示方法,我们可以把刚才程序中的a和c按照十六进制输出

printf("%llx/n%llx/n",a,c);

结果是

31c0000000000007
31200000000aae60

可见,两个相等的十进制浮点数的BID表示不一定是相同的。也就是说,一个数有多种表示方法。

a的表示里,最低位的16进制数就是7,而c的表示里,最低的5位15进制数aae60,其实就是十进制的700000。看来这后面的就是有效数字部分了。查一下BID的表示方法,还是比较复杂的,有6种情况。最高位是符号位,这里当然是0.符号位后面的两位是00,01,或10时,64位BID每个位的意义是这样的,s后面的2位和之后的8位是指数部分,之后53位T和t都是有效数字部分

s 00eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
s 01eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
s 10eeeeeeee TTTtttttttttttttttttttt tttttttttttttttttttttttttttttt
而如果符号位后面的两位是11,那么每一位的意义是
s 11 00eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
s 11 01eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
s 11 10eeeeeeee (100)Ttttttttttttttttttttt tttttttttttttttttttttttttttttt
这时,有效数字前面就加了隐含的100.
这个BID表示的数的值就是 (-1)^S *T*10^(E-398) ,其中T 是实际的有效数字(就是说如果有隐含的100需要加上后计算),E是指数,T,E都是2进制表示的
还是回到我们的例子
a的二进制数
0 0110001110 00000 00000000 00000000 00000000 00000000 00000000 00000111
指数部分就是0110001110,也就是398,所以a就是 7*10^(398-398) ,也就是7

而c的二进制是
0 0110001001 00000 00000000 00000000 00000000 00001010 10101110 01100000
指数部分是 0110001001,也就是393, 所以c的值是 700000*10^(393-398), 还是7.
这就能看明白为啥同样是7,二进制表示却不同。这也是十进制浮点和二进制浮点一个不同之处,十进制浮点没有规定一定要是哪一种表示。这也给相等比较带来了一点麻烦。

power6 里面内置了十进制浮点计算单元,而power6上面的编译器也就支持了内置的十进制浮点类型。前面已经说了,power上面的十进制浮点才用的是DPD表示方法。还是看个程序吧。下面这个程序在一个使用Power6的P520机器上,操作系统是AIX5.3 ML6, 用xlc 10.2编译。_Decimal64就是64位的十进制浮点。

int main(int argc, char **argv) { long i, count; double dfund, dinterest; _Decimal64 Dfund, Dinterest; /* 定义十进制浮点类型的变量*/ long long value; union trans{ _Decimal64 dv; int av[2]; } transTemp; dfund = atof(argv[1]); dinterest = atof(argv[2]); Dfund = atodecimal(argv[1]); Dinterest = atodecimal(argv[2]); count = atoi(argv[3]); /*下面把_Decimal64 类型的Dinterest转换成两个int,然后按照十六进制格式显示*/ transTemp.dv = Dinterest; printf("value=%#x,%#x/n",transTemp.av[0],transTemp.av[1]); printf("double fund=%20.10f interest=%40.30f/n",dfund,dinterest); printf("Decimal fund=%20.10Df interest=%40.30Df/n",Dfund,Dinterest); /* printing them with the new printf specifiers */ for(i=0;i<count;i++) { dfund=dfund*dinterest; Dfund=Dfund*Dinterest; /* performing maths */ } printf("Print final funds/n"); printf("double fund=%30.10f/n",dfund); printf("Decimal fund=%30.10Df/n",Dfund); }

其中 atodecimal是自己写的一个帮助函数

_Decimal64 atodecimal(char *s) { _Decimal64 top=0, bot=0, result; int negative=0, i; if( s[0] == '-') { negative=1; s++; } if( s[0] == '+') s++; for(; isdigit(*s); s++) { top = top * 10; top = top + *s - '0'; } if(*s == '.') { s++; for(i=strlen(s)-1; isdigit(s[i]);i--) { bot = bot / 10; bot = bot + (_Decimal64)(s[i] - '0')/(_Decimal64)10; } } result = top + bot; if(negative) result = -result; return result; }

这个程序用xlc 10.2编译时,跟上参数表示使用硬件十进制浮点。不过这样会导致编译出来的可执行文件在power5以前的cpu上无法运行。

运行的时候输入参数 ./dfp_hw 1 1.00000091 6000000

dfp_hw是程序的名字,1就是程序里面的 fund,1.00000091是interest,也就是利息,6000000是count,输出结果:

value=0x22180000,0x800001b
double fund= 1.0000000000 interest= 1.000000910000000020616539586630
Decimal fund= 1.0000000000 interest= 1.000000910000000000000000000000
Print final funds
double fund= 235.0968403429
Decimal fund= 235.0968403137

可以看到用double存储利息,再输出,就不再是1.00000091了,后面有一点误差。而用_Decimal64存储输入结果,再输出,是一点误差都没有。

然后把interest乘6000000次,也就是1.0000091的6000000次方,输出的结果误差就比较明显了。用windows自带的计算器可以验证,_Decimal64的结果是正确的。

现在来看看1.00000091的二进制表示。也就是0x22180000,0x800001b,注意这里这个power机器是大端的,所以前面以前是高4字节,后面是低4字节。连起来看,就是0x22180000 0800001b也就是

00100010 00011000 00000000 00000000 00001000 00000000 00000000 00011011

DPD表示方法也比较复杂,从高位开始看,第一位还是符号位0,DPD的规定如果符号位后面的两位是00,01,或者10,那么每一位的意义如下

s 00 mmm (00)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
s 01 mmm (01)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
s 10 mmm (10)eeeeeeee (mmm)[tttttttttt][tttttttttt][tttttttttt][tttttttttt][tttttttttt]
其中,e是指数,e的表示方法跟前面的BID方式很像。t和m是有效数字,其中,每10位t组成一个declet,表示一个3位的十进制数。m实际的位置是在第4位到第6位,但是它逻辑上的位置是在那些t前面,所以用()表示放到e的后面。

因为2的10次方是1024,刚好能表示10的3次方。但是表示起来还是需要点技巧的,declet表示三位十进制数的规则比较复杂,这也是这个表示方法叫Densely Packed Decimal(密集十进制数)的原因。下表是编码的方式。b9-b0代表10个二进制数,d2 d1 d0代表3个十进制数。

b9

b8

b7

b6

b5

b4

b3

b2

b1

b0

d2

d1

d0

编码值

数位的模式

a

b

c

d

e

f

0

g

h

i

0abc

0def

0ghi

(0 7) (0 7) (0 7)

3 位小数字

a

b

c

d

e

f

1

0

0

i

0abc

0def

100i

(0 7) (0 7) (8 9)

两位小数字,一位大数字

a

b

c

d

e

f

1

0

1

i

0abc

100f

0dei

(0 7) (8 9) (0 7)

a

b

c

d

e

f

1

1

0

i

100c

0def

0abi

(8 9) (0 7) (0 7)

a

b

c

1

0

f

1

1

1

i

0abc

100f

100i

(0 7) (8 9) (8 9)

一位小数字,两位大数字

a

b

c

0

1

f

1

1

1

i

100c

0abf

100i

(8 9) (0 7) (8 9)

a

b

c

0

0

f

1

1

1

i

100c

100f

0abi

(8 9) (8 9) (0 7)

x

x

c

1

1

f

1

1

1

i

100c

100f

100i

(8 9) (8 9) (8 9)

三位大数字

就我们的例子来看一下,最低的10位是0000011011,看b3b2b1,这里是101,所以就是上表第3行的情况,三位数字就是 (0000)(1001)(0001)也就是091,然后看从低位数的第3个10位二进制数,也就是00100000000,这显然是第一种情况,也就是100,连起来就是100000091,指数部分是390,那么这个十进制的值就是 10^(390-398)*100000091 = 1.00000091.

通过这个简单的例子,就应该对DPD方式的十进制浮点表示方式有个大概的了解了。这个方式算起来比较麻烦,所以除非有硬件支持,软件模拟的方式都不会使用的,但是DPD转换成十进制浮点的字符串表示就会很方便。

相关文章:

  • python工作可以做什么菜_python3 特色菜
  • Evolution3D 的svn搬迁。
  • python网络自动化实例_python 自动化之路 day 08_2 网络编程
  • 新年了。今年Evolution3D要做的事
  • python组合数计算cmnn_求计算机大佬解答python题
  • hex字符串转byte数组 c_高频golang面试题:字符串转成byte数组,会发生内存拷贝吗?
  • 程序员考核的五大死因(上)
  • html怎么做图片自动轮播_自动果蔬售卖机作为载体的社区新零售怎么做?
  • 移动设备管理与OMA DM 协议 V5(3)
  • python直接调用类方法_python中可以直接用类调用方法吗
  • liferay5.2.3 删除7cogs样例
  • c++ class struct同名_结构体的各种使用方法详细讲解-cc++
  • python这天第几天_Python编程实现输入某年某月某日计算出这一天是该年第几天的方法...
  • RAID级别简介
  • 稀疏光流python_opencv3/Python 稠密光流calcOpticalFlowFarneback详解
  • Android Studio:GIT提交项目到远程仓库
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • AWS实战 - 利用IAM对S3做访问控制
  • Elasticsearch 参考指南(升级前重新索引)
  • Golang-长连接-状态推送
  • IndexedDB
  • Linux gpio口使用方法
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • Sass 快速入门教程
  • 闭包,sync使用细节
  • 漂亮刷新控件-iOS
  • 实现简单的正则表达式引擎
  • 一天一个设计模式之JS实现——适配器模式
  • 回归生活:清理微信公众号
  • 扩展资源服务器解决oauth2 性能瓶颈
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​低代码平台的核心价值与优势
  • # Maven错误Error executing Maven
  • #、%和$符号在OGNL表达式中经常出现
  • #我与Java虚拟机的故事#连载13:有这本书就够了
  • #在 README.md 中生成项目目录结构
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (1)常见O(n^2)排序算法解析
  • (20050108)又读《平凡的世界》
  • (Bean工厂的后处理器入门)学习Spring的第七天
  • (BFS)hdoj2377-Bus Pass
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (第8天)保姆级 PL/SQL Developer 安装与配置
  • (二)hibernate配置管理
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • (学习日记)2024.01.19
  • (转)3D模板阴影原理
  • (转)我也是一只IT小小鸟
  • (转)一些感悟
  • ./configure,make,make install的作用(转)
  • .Net Web项目创建比较不错的参考文章
  • :=
  • @PreAuthorize注解
  • [ 蓝桥杯Web真题 ]-布局切换