2016年3月22日火曜日

数値を四捨五入して文字変数に格納したい場合、PUT関数を使うと想定外の結果になる。



数値を四捨五入して文字変数に格納したい場合、PUT関数を使ってませんか?
以下の例を見てみましょう。

想定通りの結果
data DT1;
    length B $10.;
    A = 1.25;
    B = left(put(A, 12.1));
    put B=;
run;

ログ
B=1.3

小数第2位を四捨五入して文字化した変数が出来ました。
次はどうでしょう。


想定外の結果
data DT1;
    length B $10.;
    A = 4.1-4.05;
    B = left(put(A, 12.1));
    put B=;
run;

ログ
B=0.0

4.1-4.05 = 「0.05」 なので、小数第2位を四捨五入すると「0.1」となるはずが、「0.0」になりました。

これは浮動小数点誤差ってやつのせいですね。
http://sas-tumesas.blogspot.jp/2014/03/blog-post_14.html (データステップ)100万回)
http://blog.n-insight.co.jp/2015/05/blog-post_27.html (NIBLOG)


つまり「4.1-4.05」は「0.05」ではなく「0.049999999・・・」みたいな値で格納されてます。
だからPUT関数で「0.0」という値が帰ってきたわけですね。



で、これをどうするかは各自(各社)いろいろ考え・やり方があると思います。
よくROUND関数をかませたりしますが、、、
data DT1;
    length B $10.;
    A = 4.1-4.05;
    B = left(put( round(A,0.1) ,8.1));
    put B=;
run;

ログ
B=0.1



📝ROUND関数には注意すべき動きがある

ROUND関数は「第1引数を第2引数の最も近い倍数に丸める関数」ですが、
その独特の動きや制限も重要です。以下にSAS社のドキュメントの記述を抜粋します
(※日本語ドキュメントは誤記がある?のでちょっと分かりにくいですが英語の方を示します。。)


The Effects of Rounding
Rounding by definition finds an exact multiple of the rounding unit that is closest to the value to be rounded. For example, 0.33 rounded to the nearest tenth equals 3*0.1 or 0.3 in decimal arithmetic. In binary arithmetic, 0.33 rounded to the nearest tenth equals 3*0.1, and not 0.3, because 0.3 is not an exact multiple of one tenth in binary arithmetic.

The ROUND function returns the value that is based on decimal arithmetic, even though this value is sometimes not the exact, mathematically correct result. In the example ROUND(0.33,0.1), ROUND returns 0.3 and not 3*0.1.

「第1引数を第2引数の最も近い倍数に丸める」処理において、10進数の算術と2進数の算術では「正確な倍数」の扱いの違いがあって、そこをROUND関数では調整してるような事が書かれているのでしょうか(間違っていたらご指摘をお願いします)


Producing Expected Results
In general, ROUND(argument, rounding-unit) produces the result that you expect from decimal arithmetic if the result has no more than nine significant digits and any of the following conditions are true:
  • The rounding unit is an integer.
  • The rounding unit is a power of 10 greater than or equal to 1e-15. (If the rounding unit is less than one, ROUND treats it as a power of 10 if the reciprocal of the rounding unit differs from a power of 10 in at most the three or four least significant bits.)
  • The result that you expect from decimal arithmetic has no more than four decimal places.

期待される結果を生成する条件が示されています。思ったより扱える範囲が狭い?
ちなみに文中の、significant digits と decimal places の意味を混同してるケースをよく見かけるので注意。



その他の挙動についてもドキュメントに書かれているので、要確認!
ROUND関数を単純に四捨五入するやつと思っていると、想定外の結果になるかもしれません。
例えば以下のように最後の桁が「8」か「9」かで結果が異なっています。
data DT1;
 A = round(1.049999999998,0.1);
 B = round(1.049999999999,0.1);
 put A= B=;
run;

ログ
A=1 B=1.1



というわけで、一概にこうすればいいってのはない気がします。
「私はこうしてます」 とか何かアイディアがあったら教えてほしいです。

0 件のコメント:

コメントを投稿