SAS 数值存储方式和精度问题
本文链接:https://www.cnblogs.com/snoopy1866/p/16021137.html
1 数值存储方式
SAS使用8个字节存储数值,使用浮点计数法表示数值。
浮点计数法由4个部分组成:符号位(Sign)、基数(Base)、指数(Exponent)、尾数(Mantissa),这4个部分分别提供以下作用:
- 符号位:代表该数值是正数还是负数
- 基数:SAS系统中的基数默认为2。
- 指数:代表基数被乘的倍数
- 尾数:定义数值范围的一个小数
将计算机内部存储的数值转化为10进制数值的公式为:\(\color{red}{Sign}\)*(\(\color{purple}{Mantissa}\) * Base$\color{green}{Exponent}$)
下图展示了SAS用8个字节表示浮点数的具体情况:

即第1位为符号位,第2-12位为指数位,13-64位为尾数位,符号位中,0表示正数,1表示负数。
例如:Sign = 1, Exponent = 3, Mantissa = 1492,在10进制下代表:(-1) * (0.1492 * 103) = -149.2,在2进制下表示:(-1) * (0.1492 * 22) = -1.1936;
2 产生的问题
不是所有浮点数都能用8个字节精确地表示,某些浮点数甚至无法用有限个字节精确地表示,这在任何进制下都是存在的问题。
例如:10进制下的0.1在16进制下表示为3FB999999999999999999999999999999...,2/3在10进制下表示为0.666666666666666666666...
当计算机面对这种情况时,有两种选择:
(1)Truncation:2/3+2/3+2/3 = 0.666666 + 0.666666 + 0.666666 = 1.999998
(2)Rounding: 2/3+2/3+2/3 = 0.666667 + 0.666667 + 0.666667 = 2.000001
但这两种选择都无法完美解决精度问题,而且随着运算次数的累计,精度问题会愈发凸显。
来看下面一个例子:
/*DO 迭代生成0.1~0.9*/
data s1;
do a = 0.1 to 0.9 by 0.1;
index + 1;
output;
end;
run;
/*手动输入0.1~0.9*/
data s2;
do b = 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9;
index + 1;
output;
end;
run;
/*合并s1-s2*/
data s3;
merge s1 s2;
by index;
if a = b then A_eq_B = "Y";
run;
proc print data = s3;
var index a b A_eq_B;
run;

可以看到PROC PRINT打印出来的结果中显示,A,B两列显示完全一致,但其中0.3,0.8和0.9却出现了A≠B的情况。
为了能够看到SAS内部储存的实际数值,使用十六进制输出格式输出结果:
proc print data = s3;
var index a b A_eq_B;
format a hex16. b hex16.;
run;

由此可见,是DO循环迭代计算导致的误差累计,最终导致A和B两列实际存储的数值存在微小差异。
这种精度损失可能会在下述场景中导致问题:
- 使用 IF,SELECT,WHERE语句比较两个不同计算方法得出的数值,或与显式指定的数值比较时;
- 对数据集取子集时;
- 使用 PROC COMPARE 过程比较两个数据集时
- 对基于计算产生的浮点数进行分类分析时
- PROC REPORT或其他过程步的输出可能会显示出奇怪的小数(例如:-0.00)
3 解决办法
以下3种方式可以解决SAS中存在的大多数精度问题:
- 使用 ROUND 函数
- 为数值变量创建字符串版本的变量
- 利用过程步中的选项
(1)ROUND函数
在不需要特别精确的情况下,可以使用ROUND函数仅比较前几位小数。
data s3;
merge s1 s2;
by index;
a_r = round(a, 0.1);
b_r = round(b, 0.1);
if a_r = b_r then A_eq_B = "Y";
run;
proc print data = s3;
var index a b A_eq_B a_r b_r;
format a hex16. b hex16. a_r hex16. b_r hex16.;
run;

(2)创建数值变量的字符串版本
data s3;
merge s1 s2;
by index;
a_fmt = put(a, 3.1);
b_fmt = put(b, 3.1);
if a_fmt = b_fmt then A_eq_B = "Y";
run;
proc print data = s3;
var index a b A_eq_B a_fmt b_fmt;
format a hex16. b hex16. a_fmt $hex16. b_fmt $hex16.;
run;

使用过程步中的选项
proc compare base = s1 compare = s2(rename = (b = a)) criterion = 0.1 method = absolute;
run;

参考文献:https://www.jianguoyun.com/p/DfN3qOQQ6YbXCRimmbME
SAS 数值存储方式和精度问题的更多相关文章
- 从java toBinaryString() 看计算机数值存储方式(原码、反码、补码)
一.toBinaryString 方法及其含义 1.1 方法说明 该方法位于java.lang.Integer类中 方法签名:public static String toBinaryString(i ...
- JS007. 深入探讨带浮点数运算丢失精度问题(二进制的浮点数存储方式)
复现与概述 当JS在进行浮点数运算时可能产生丢失精度的情况: 从肉眼可见的程度上观察,发生精度丢失的浮点数是没有规律的,但该浮点数丢失精度的问题会100%复现.经查阅,这个问题要追溯至浮点数的二进制存 ...
- float浮点数的二进制存储方式及转换
int和float都是4字节32位表示形式.为什么float的范围大于int? float精度为6-7位.1.66*10^10的数字结果并不是166 0000 0000 指数越大,误差越大. 这些问题 ...
- python 数据处理中各种存储方式里数据类型的转换
自己记录,仅供参考 在数据处理时经常会遇到数据类型不匹配的事情,为了方便查看各种存储方式中数据类型的改变.我把一些自己常用的整理方式记录下来,希望可以为以后数据类型的处理工作提供便利. 数据常用的基本 ...
- c语言中float、double、long double在内存中存储方式
存储格式中的二机制转为浮点数: 浮点型变量在计算机内存中占用4个字节(4 Byte),即32-bit,一个浮点数由2部分组成:底数m 和 指数e: 底数部分:使用2进制数来表示此浮点数的实际值: 指 ...
- C语言 float、double数据在内存中的存储方式
float在内存中占4个字节(32bit),32bit=符号位(1bit)+指数位(8bit)+底数位(23bit) 指数部分 指数位占8bit,可以表示数值的范围是0-(表示0~255一共256个数 ...
- C语言浮点数存储方式
对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用 32bit,double数据占用 64bit.其实不论是float类型还是double类型,在计算 ...
- .NET C#教程初级篇 1-1 基本数据类型及其存储方式
.NET C# 教程初级篇 1-1 基本数据类型及其存储方式 全文目录 (博客园).NET Core Guide (Github).NET Core Guide 本节内容是对于C#基础类型的存储方式以 ...
- C/C++浮点数在内存中的存储方式
一.内存表示 任何数据在内存中都是以二进制的形式存储的,浮点数的表示是把一个数的有效数字和数的范围在计算机的一个存储单元中分别予以表示,数的小数点位置随比例因子的不同而在一定范围内自由浮动.如下图是3 ...
随机推荐
- NSSet和NSMutableSet - By吴帮雷
1.NSSet的使用 [NSSet setWithSet:(NSSet *)set]; 用另外一个set对象构造 [NSSet setWithArray:(NSArray *)array];用数组构造 ...
- H5架构和原生架构的区别
1.App的3种开发方式 表面上看,手机App都是同样的东西,就是手机上的应用程序,点击图标就能运行,但是它们的底层技术不一样.按照开发技术,App可以分成三大类.原生应用(简称nativeApp), ...
- 2018 PHP面试题
2018 PHP面试题 题目来自<PHP程序员面试笔试宝典>,里面涵盖了近三年了各大型企业常考的PHP面试题,针对面试题提取出来各种面试知识也涵盖在了本书. 1.PHP常考基础 1.PHP ...
- Asp.Net Core: Swagger 与 Identity Server 4
Swagger不用多说,可以自动生成Web Api的接口文档和客户端调用代码,方便开发人员进行测试.通常我们只需要几行代码就可以实现这个功能: ... builder.Services.AddSwag ...
- win10 VScode配置GCC(MinGW)
前提 安装 Visual Studio Code 安装 C/C++ 扩展 for VS Code 也可以在vscode的extension界面搜索'c'查找插件安装 获取最新的 Mingw-w64 , ...
- 轻量级DI框架Guice使用详解
背景 在日常写一些小工具或者小项目的时候,有依赖管理和依赖注入的需求,但是Spring(Boot)体系作为DI框架过于重量级,于是需要调研一款微型的DI框架.Guice是Google出品的一款轻量级的 ...
- mysql5.7下载
官网:https://dev.mysql.com/doc/refman/5.7/en/installing.html 二进制安装:https://dev.mysql.com/doc/refman/5. ...
- ctf linux 基本命令
从网站上下载一个tar.gz的安装包,在linux下解压缩. 获得一个flag的二进制文件. 通过linux的grep命令搜索到key. grep命令参数中文说明
- [Unity] 编辑器运行中动态编译执行C#代码
(一)问题 之前写Lua时,修改完代码 reload 就可以热重载代码,调试起来十分方便(重构则十分痛苦). 现在使用 C# 做开发,目前还没找到比较方便地进行热重载的方式.只能退而求其次,在调试上找 ...
- textbox 实现跨操作系统换行的两种写法
每个操作系统对换坏的解释都不一样.所以写代码的时候要注意这个细节: 要基于.net跨环境的基类去写,才能跨平台. Unix系统里,每行结尾只有"<换行>",即" ...