最近经常被一个问题苦恼,就是sas在保留小数位的时候经常会出错,我个人偏爱put,但put有的时候会出问题。所以我想,在保留时如果先把小数位放开到最多,然后先变字符型再变回数值,就可以有效保证put后不会出问题。
但如果这样改的话会很麻烦,因为我已经有很多写好的宏,于是考虑是否可以用自定义函数。这样,只要新函数的参数和put保持一致,就可以通过全文替换,直接完成修改。经过测试,自定义函数可以用语sql,这是个好事。
想起之前我写过一个保留有效数字的方法,突然想不如一起集合在一个函数上,于是新函数设计了3个参数,前两个和put一样。第三个参数如果是0则为保留小数,如果是1则是保留有效数字。当参数为1时,第二个参数只能是正整数。
proc fcmp outlib=work.funcs.putd;
/* 定义函数 */
function putd(var, informat , roundOption) $200;
length result $200; /* 假设转换后的字符不会超过200个字符 */
/* informat 参数应转换为SAS格式名称 */
formatName = cats(informat);
/* 根据 roundOption 的值处理变量 */
if roundOption = 0 then do;
result = cats(putn(round(var,0.1**30), formatName)); /* 四舍五入 informat为数值型*/
end;
else if roundOption = 1 then do;/* 保留有效数字 informat为正整数*/
length ornumc $200;
if int(var)^=var then ornumc=strip(put(var,best.));
else ornumc=cats(put(var,best.),'.');
dot=index(strip(ornumc),'.');
efb=prxmatch('/[1-9]/',strip(ornumc));
efe=efb+informat;
if efb < dot <= efe then efe=efe+1;
if efe > dot > 1 then result=putn(var,cats('30.',efe-dot-1,'-l'));
else if dot > 1 then result=strip(putn(round(var,10**(dot-1-informat)),cats('32.',formatName)));
end;
else do;
result = 'Invalid Option'; /* 无效的选项 */
end;
return(result); /* 返回结果 */
endsub;
run;
quit;
/* 从编译的函数库中引用函数 */
options cmplib=(work.funcs);
data _null_ aaaa;
num_var = 123.456; /* 测试的数值 */
char_var = putd(num_var, 12.1, 0); /* 调用函数进行转换 */
put char_var=; /* 输出转换结果 */
run;
本来想把putd函数的第3个参数设默认值的,后来发现无法设定。所以我自己已经把上面这个拆成2个函数了。大家看自己需要吧。
再有就是这段程序会在work下生成一个名为Funcs的数据集,这个数据集存在,才可以使用新函数。建议定义的时候把这个数据集定义到一个其他逻辑库下,这样可以防止被误删。
************************************************************************************************
需要补充一点,本来我在下面一步中,打算使用put的:先用put变成字符,然后再用input换成数值,目的是先保留一个比较多的位数,然后在此基础上保留正确小数位。
result = cats(putn(round(var,0.1**30), formatName)); /* 四舍五入 informat为数值型*/
但是这个代码却有致命问题:
当数值和字符互换的时候,数值会发生损失,请看下面例子:
data _null_;
a=2.25;
b=put(a,32.25);
c=input(b,best32.);
d=a-b;
e=a=b;
f=a-c;
g=a=c;
put a=b=c=d=e=f=g=;
run;
结果为:A=2.25 B=2.2500000000000000000000000 C=2.25 D=4.440892E-16 E=0 F=4.440892E-16 G=0
可以看到,2.25通过put变成字符,再换回数值后,与原来的2.25相比,差值(F的值)不为0。这种情况原因不明,但可以肯定,会发生在本来小数位不多,但放出小数位很多的情况下。上例中,如果把b=put(a,32.25);换成b=put(a,32.24);,则F的值就会变成0,也就是说在2.25这个数中,25位就是一个极限值 。
但是极限值和数值有关,如果把2.25换掉,则这个极限值也会改变。但两者关系目前并不清楚,有机会我再测试一下。