The Nameless City

何故か製薬やSAS関連のブログ、の予定。

有効桁数で丸めた上で文字列に変換するマクロや自作関数とか

マクロ

自作マクロ故に担保はしない。
使う前に適当にテストしてほしい。
なお、指摘受け付ける。
著作権については、好きにしていいよ。

使い方

%mround_digit :
[出力先の文字変数]=%mround_digit([元データになる数値変数], [有効桁数]) ;

[有効桁数]は1以上の整数。

マクロ中身

%macro mround_digit(var, digit) ;
ifc(
        &var.=0
        ,ifc(&digit=1,"0","0." || strip(repeat("0",&digit.-2))),
        strip(putn(
            round(
                 &var. 
                ,10 ** 
                    ifn(
                        int(log10(abs( &var.)))>=0
                        ,int(log10(abs( &var.)))-&digit.+1
                        ,int(log10(abs( &var.)))-&digit.
                    )
            )
            ,'12.'
                || strip(putn(
                    ifn(
                        ifn(
                            int(log10(abs( &var.)))>=0
                            ,int(log10(abs( &var.)))-&digit.+1
                            ,int(log10(abs( &var.)))-&digit.
                        ) >0
                        ,0
                        ,- ifn(
                            int(log10(abs( &var.)))>=0
                            ,int(log10(abs( &var.)))-&digit.+1
                            ,int(log10(abs( &var.)))-&digit.
                        )
                        )
                    ,'12.0'
                ))
        ))
    ) 
%mend mround_digit ;

data TEST ;
    input a best12. ;
cards ;
1.10122
112.222
3332001
-1.10122
-112.222
-3332001
0.002455
0
1
10
1515151
115.151
;
run ;

data RESULT ;
    set TEST ;
    attrib b c d e f length=$200. ;
    b=%mround_digit(a,1) ;
    c=%mround_digit(a,2) ;
    d=%mround_digit(a,3) ;
    e=%mround_digit(a,4) ;
    f=%mround_digit(a,8) ;
run ;

proc print data=RESULT ;
    format a best12. ;
quit ;

結果

                                    SAS システム                 2020年 9月29日 火曜日 16時05分24秒   5

Obs               a    b           c           d           e                f

  1         1.10122    1           1.1         1.10        1.101       1.1012200
  2         112.222    100         110         112         112.2       112.22200
  3         3332001    3000000     3300000     3330000     3332000     3332001.0
  4        -1.10122    -1          -1.1        -1.10       -1.101      -1.1012200
  5        -112.222    -100        -110        -112        -112.2      -112.22200
  6        -3332001    -3000000    -3300000    -3330000    -3332000    -3332001.0
  7        0.002455    0.002       0.0025      0.00246     0.002455    0.0024550000
  8               0    0           0.0         0.00        0.000       0.0000000
  9               1    1           1.0         1.00        1.000       1.0000000
 10              10    10          10          10.0        10.00       10.000000
 11         1515151    2000000     1500000     1520000     1515000     1515151.0
 12         115.151    100         120         115         115.2       115.15100

余談

マクロ化しなくてもいいのだが、いわゆるワンライナー
マクロもかなりマニアックで、通常の関数のように書ける形にした為に、セミコロンを中で使ってなかったりするし、変数に格納した方が分かりやすいところもあえてコピペだし、関数もifc/ifn/putn/repeatはあんまり使われていないとは思われる。難しい関数ではないのだけど。
ワンライナーがゆえに、0とかに対応するところでは、ログに「関数の引数が無効」とか「欠損値を含んだ計算により~」とか沢山出て来る。そこは諦めてほしい。
普通のステートメントで分岐させると出さなくて済むようにはなるんだが、ワンライナーの魅力はあるからなあ。

FCMPで関数自作

普通のプログラム言語のように関数が作れるのだが、便利と言えば便利だが、デバッグライトが分からんのでどう作るかが悩みの一つ。
もうひとつは、あまりにもあっさり自作出来るのだが、標準関数とパッと見分けつかないので困るなというところか。
なお、関数を使うのに、システムオプションでCMPLIBを設定する必要がある。

作り方

proc fcmp outlib=work.funcs.math ;
    function round_digit(var, digit) $ varchar(32767) ;
        attrib str length=varchar(32767) ;
        str=" " ;
        if var=0 then do ;
            if digit=1 then do ;
                str="0" ;
            end ;
            else do ;
                str="0." || strip(repeat("0",digit-2)) ;
            end ;
        end ;
        else do ;
            attrib mult rd rd2 var2 length=8 ;
            attrib fmt length=varchar(32767) ;
            mult=int(log10(abs( var))) ;
            if mult >= 0 then do ;
                rd = mult - digit + 1 ;
            end ;
            else do ;
                rd = mult - digit ;
            end ;
            if rd > 0 then rd2 = 0 ;
            else rd2 = - rd ;
            var2 = round(var,10**rd) ;
            fmt = '12.' || strip(put(rd2,12.0)) ;
            str = strip(putn(var2,fmt)) ;
        end ;

        return(str);
    endsub;
quit ;

options cmplib=work.funcs;

data TEST ;
    input a best12. ;
cards ;
1.10122
112.222
3332001
-1.10122
-112.222
-3332001
0.002455
0
1
10
1515151
115.151
;
run ;

data RESULT ;
    set TEST ;
    attrib b c d e f length=$200. ;
    b=round_digit(a,1) ;
    c=round_digit(a,2) ;
    d=round_digit(a,3) ;
    e=round_digit(a,4) ;
    f=round_digit(a,8) ;
run ;

proc print data=RESULT ;
    format a best12. ;
quit ;

結果

                                    SAS システム                 2020年 9月29日 火曜日 19時14分00秒  25

Obs               a    b           c           d           e                f

  1         1.10122    1           1.1         1.10        1.101       1.1012200
  2         112.222    100         110         112         112.2       112.22200
  3         3332001    3000000     3300000     3330000     3332000     3332001.0
  4        -1.10122    -1          -1.1        -1.10       -1.101      -1.1012200
  5        -112.222    -100        -110        -112        -112.2      -112.22200
  6        -3332001    -3000000    -3300000    -3330000    -3332000    -3332001.0
  7        0.002455    0.002       0.0025      0.00246     0.002455    0.0024550000
  8               0    0           0.0         0.00        0.000       0.0000000
  9               1    1           1.0         1.00        1.000       1.0000000
 10              10    10          10          10.0        10.00       10.000000
 11         1515151    2000000     1500000     1520000     1515000     1515151.0
 12         115.151    100         120         115         115.2       115.15100

悩ましいなあ

先に書いたが、FCMP関数で作ってしまうと、普通の関数みたいに見えすぎるのがアレ。
そして、FCMPプロシージャの使い方をまだ探り探りやってる(ヘルプにもあんまり載ってない)ところもあって、自信がない。
文字変数の定義これでいいのかも分からん(パッと見文法見つけられてない)。
マクロの方が何やってるかはまだ分かりやすいかもなあ。

ただ、この手の文法は、今後のSASを考えると増えていくとは思う。ただ、マルチスレッドプログラミングとかやる機会は治験ではなさそうだし、世間的にもそういうのを意識してコーディングしていくというのも需要ないだろうし。うーん。

追記

log10を使わずにE表記使っての対応が可能なのに気づいたが、今更なのでいいや。やってることはlog10と変わらないし。