设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

123下一页
返回列表 发新帖
查看: 6336|回复: 21
打印 上一主题 下一主题

[Access本身] 【原创 / 文章】谨慎使用单精度/双精度数值类型

[复制链接]
跳转到指定楼层
1#
发表于 2005-9-5 21:39:00 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
前言

  在近日几个帖子里面,和QQ群的讨论里面,我发现很多网友都遇到的问题都是因为不恰当地使用了单精度/双精度数值。因此想专门就这个话题谈一下。

  单精度和双精度数值类型最早出现在C语言中(比较通用的语言里面),在C语言中单精度类型称为浮点类型(Float),顾名思义是通过浮动小数点来实现数据的存储。这两个数据类型最早是为了科学计算而产生的,他能够给科学计算提供足够高的精度来存储对于精度要求比较高的数值。但是与此同时,他也完全符合科学计算中对于数值的观念:

  当我们比较两个棍子的长度的时候,一种方法是并排放着比较一下,一种方法是分别量出长度。但是事实上世界上并不存在两根完全一样长的棍子,我们测量的长度精度受到人类目测能力和测量工具精度的限制。从这个意义上来说,判断两根棍子是否一样长丝毫没有意义,因为结果一定是False,但是我们可以比较他们两个哪个更长或者更短。这个例子很好地概括了单精度/双精度数值类型的设计初衷和存在意义。

  基于上述认识,单精度/双精度数值类型从一开始设计的时候,就不是一个准确的数值类型,他只保证在他这个数值类型的精度之内是准确的,精度之外则不保证,比方说,一个数值5.1,很可能存储在单精度/双精度数值中的实际值是5.100000000001或者5.09999999999999。导致这个现象的原因我们可以通过两种方式来解释:

简单的解释方法:

  你可以尝试在任何一个控件的属性面板中,设定他的宽度为:3.2CM,当你输入完毕后,你会发现值自动变成了3.199cm,无论你怎么改,你都无法输入3.200CM,因为实际上在电脑中存储的并不是CM为单位的数值,而是“缇”为单位的数值,而“缇”和CM之间的比值,是个很难被除尽的数,因此你输入完毕后,电脑自动转换成了最接近的“缇”值,然后再转换成厘米显示到属性面板上,这一乘一除,两次四舍五入,误差就出来了。单精度/双精度也是类似的原理,其实在二进制存储的时候,单精度/双精度都采用了类似相近分数的方法,而这样的存储是不可能做到准确的。

深入的解释方法:

  让我们来看看我们存储到数字介质中的单精度/双精度值到底是怎么样的,我们使用如下代码对单精度类型进行一个解剖:

Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)





Public Sub floatTest()
Dim dblVar As Single

dblVar = 5.731 / 8
dblOutput dblVar

dblVar = dblVar * 2
dblOutput dblVar

dblVar = dblVar * 2
dblOutput dblVar

dblVar = dblVar * 2
dblOutput dblVar

dblVar = dblVar * 2
dblOutput dblVar

dblVar = dblVar * 2
dblOutput dblVar

End Sub

Public Sub dblOutput(ByVal dblVar As Single)
    Dim bytVar(3) As Byte
    Dim i As Integer, j As Integer
    Dim strVar As String

    CopyMemory ByVal VarPtr(bytVar(0)), ByVal VarPtr(dblVar), 4
    strVar = dblVar & ": "
    For i = 3 To 0 Step -1
        For j = 7 To 0 Step -1
            strVar = strVar & (bytVar(i) And 2 ^ j) / 2 ^ j
        Next j
        strVar = strVar & " "
    Next i
    Debug.Print strVarEnd Sub

  运行后我们得到输出结果(输出格式为高位左,低位右):

.716375: 00111111 00110111 01100100 01011010
1.43275: 00111111 10110111 01100100 01011010
2.86550: 01000000 00110111 01100100 01011010
5.73100: 01000000 10110111 01100100 01011010
11.4620: 01000001 00110111 01100100 01011010
22.9240: 01000001 10110111 01100100 01011010

  这里,我们把单精度类型转化成了二进制数据输出,这里我们看到,虽然这六个数字完全不同,但是他们的二进制存储惊人地相似,我们看到红色标记部分,每次都是加1,事实上,单精度数据类型使用从高位开始第1位作为正负标记位(绿色),第2位到第9位,是一个跨字节的有符号字节类型数据,这个数值决定了小数点移动的方向和位数(红色),第10
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 分享分享 分享淘帖 订阅订阅
2#
发表于 2005-9-6 09:49:00 | 只看该作者
好文章,顶!
3#
发表于 2005-9-6 11:46:00 | 只看该作者
非常的好,佩服!佩服!看了这文章才知道单/双精度数有此缺陷.
4#
发表于 2005-9-7 00:32:00 | 只看该作者
以下是引用laowu在2005-9-6 1:49:00的发言:

好文章,顶!



点击这里给我发消息

5#
发表于 2005-9-7 00:41:00 | 只看该作者
高深!
6#
 楼主| 发表于 2005-9-7 01:02:00 | 只看该作者
以下是引用wang1950317在2005-9-6 16:41:00的发言:

高深!



觉得太深你就这样理解:

1/3=0.333333333333

0.333333333333*3=0.999999999999

你输入一个1,电脑除以3给你存储个0.333333333333,然后读取的时候乘以个3显示0.999999999999。误差就出来了。

单精度/双精度的误差都是这样引起的。
7#
 楼主| 发表于 2005-9-7 01:52:00 | 只看该作者
以下是引用Grant在2005-9-6 3:46:00的发言:

非常的好,佩服!佩服!看了这文章才知道单/双精度数有此缺陷.
这个不能说是缺陷,而是特点,就像你不能说无法存储小数是整型的缺陷一样。只是因为很多人并不了解单精度/双精度数据类型的原理,错误使用所以引发了问题,这是使用者的问题。也是我发这个帖子的原因。
8#
发表于 2005-9-7 19:36:00 | 只看该作者
那用什么才能精确呢?比如:我的数据格式有三位的:0.0321.2311.2850.128那么该如何给这个字段设定类型?
9#
 楼主| 发表于 2005-9-7 20:29:00 | 只看该作者
以下是引用secowu在2005-9-7 11:36:00的发言:

那用什么才能精确呢?

比如:我的数据格式有三位的:

0.032

1.231

1.285

0.128

那么该如何给这个字段设定类型?



1、货币类型

2、都乘以1000存储。其实货币类型本身就是浮点整数。

[此贴子已经被作者于2005-9-7 12:29:36编辑过]

10#
发表于 2005-9-8 16:30:00 | 只看该作者
我将一个字段数据类型设置为单精度后,导出包含该字段的查询为文本文件,发现有小数的字段值,如100.21,变成100.20,各个数据都是少0.01,后来改成双精度,好像再也没有发生该现象。我用的数字都是2位小数,不知道用双精度合适吗?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-6-5 04:08 , Processed in 0.130625 second(s), 34 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表