C语言十进制浮点数,十进制浮点数的表示方法

本文探讨了十进制浮点数在金融领域的重要性,IEEE754-2008的规定以及两种表示方法(DPD和BID)。重点介绍了BID的复杂表示机制,并通过实例展示了Power6和x86/x64处理器上的十进制浮点运算差异。
摘要由CSDN通过智能技术生成

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

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

Name

Common name

Base

Digits

E min

E max

binary16

Half precision

2

10+1

-14

15

binary32

Single precision

2

23+1

-126

127

binary64

Double precision

2

52+1

-1022

1023

binary128

Quadruple precision

2

112+1

-16382

16383

decimal32

10

7

-95

96

decimal64

10

16

-383

384

decimal128

10

34

-6143

6144

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

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

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

所以Power6上有了硬件的十进制浮点计算单元,就用DPD表示.而在x86 x64 cpu上没有十进制计算单元, 各种软件实现的十进制浮点库默认大都用BID方式表示.比如Intel就实现了一个开源的c 语言的十进制浮点数库。

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

#include

<

stdio

.

h

>

int

main

(

)

{

double a

=

7

.

;

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

[

]

,

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

=

;

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

=

,

bot

=

,

result

;

int

negative

=

,

i

;

if

(

s

[

]

=

=

'

-

'

)

{

negative

=

1

;

s

+

+

;

}

if

(

s

[

]

=

=

'

+

'

)

s

+

+

;

for

(

;

isdigit

(

*

s

)

;

s

+

+

)

{

top

=

top

*

10

;

top

=

top

+

*

s

-

'

'

;

}

if

(

*

s

=

=

'

.

'

)

{

s

+

+

;

for

(

i

=

strlen

(

s

)

-

1

;

isdigit

(

s

[

i

]

)

;

i

-

-

)

{

bot

=

bot

/

10

;

bot

=

bot

+

(

_Decimal64

)

(

s

[

i

]

-

'

'

)

/

(

_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

g

h

i

0abc

0def

0ghi

(0

7) (0

7) (0

7)

3

位小数字

a

b

c

d

e

f

1

i

0abc

0def

100i

(0

7) (0

7) (8

9)

两位小数字,一位大数字

a

b

c

d

e

f

1

1

i

0abc

100f

0dei

(0

7) (8

9) (0

7)

a

b

c

d

e

f

1

1

i

100c

0def

0abi

(8

9) (0

7) (0

7)

a

b

c

1

f

1

1

1

i

0abc

100f

100i

(0

7) (8

9) (8

9)

一位小数字,两位大数字

a

b

c

1

f

1

1

1

i

100c

0abf

100i

(8

9) (0

7) (8

9)

a

b

c

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转换成十进制浮点的字符串表示就会很方便。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的通用编程语言,它具备高效、低级内存操作能力以及强大的系统级功能。C语言的设计目的是为了提供一种简洁高效的语法,并且能够直接访问计算机硬件资源。 BCD (Binary-Coded Decimal)编码是一种将十进制数字转换成二进制表示的方式,每个十进制数字使用4位二进制码来表示。例如,“5”会被表示为“0101”,“9”则为“1001”。这种方式特别适用于需要精确计算和显示小数值的场合,比如财务运算、计算器等设备。 在C语言中,将十进制数转换为BCD的过程可以分为以下几个步骤: 1. **读取输入**:首先从用户或文件获取待转换的十进制整数。 2. **分离每位数字**:逐位读取输入的每一位数字。 3. **转换为BCD形式**:对于每一位读取到的数字,将其转换为4位二进制码。 - 例如,将数字`5`转换为BCD,即`0101`。 4. **组合所有数字的BCD形式**:将每一单个数字的BCD形式通过适当的方式连接起来形成最终的BCD数串。 - 如果有多个数字,则在每个数字的BCD表示之间添加适当的空格以保持数字之间的区分。 5. **输出结果**:将最终形成的BCD数串打印出来。 下面是一个简单的示例程序演示如何将整数转换为BCD形式: ```c #include <stdio.h> void to_bcd(int num, char *bcd) { int i; for (i = 0; num > 0; ++i) { bcd[i] = (num % 10 + '0'); num /= 10; } // 将十进制数组翻转为正确的BCD格式 for (int j = 0, k = strlen(bcd) / 2; j < k; ++j, --k) { char temp = bcd[j]; bcd[j] = bcd[k]; bcd[k] = temp; } bcd[i] = '\0'; // 添加字符串结束符 } int main() { int number; char bcd; // 最大两个数字的BCD表示,包括可能出现的空格 printf("请输入一个十进制数: "); scanf("%d", &number); to_bcd(number, bcd); printf("对应的BCD表示为: %s\n", bcd); return 0; } ``` 上述代码段首先定义了一个函数 `to_bcd` 来处理转换过程。然后,在 `main` 函数中,通过用户输入接收一个十进制数,并使用这个函数将其转换为BCD表示。最后,将转换后的BCD数输出到控制台。 --- 相关问题: 1. 在实际应用中为什么要使用BCD数而非普通的二进制表示? 2. 对于浮点数或更复杂的数值类型,是否有类似BCD转换的功能,或者有哪些替代方案? 3. BCDF(Binary-Coded Float)是如何对浮点数进行BCD编码的?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值