Page icon

第281回

2023/07/31
Empty
質問タイム
Empty
Empty
Empty
Empty
Empty
12 more properties
本日も、ひと通り見終えた感じの個人的にお気に入りな技術ブログ「その Swift コード、こう書き換えてみないか」の余興的に続く事項の最後のところから、 
NotificationCenter
 を Swift Concurrency で扱うまわりを見ていく回にしますね。よろしくお願いします。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #281
00:00 開始 00:10 浮動小数点数を 0 と比較して良い? 02:21 ulpOfOne 04:17 NSDecimalNumber による誤差の抑制 05:00 浮動小数点数の誤差を考慮した比較 06:37 Decimal 型の有効桁数は 38 桁 07:55 Decimal 型の作りを見てみる 09:13 Decimal であろうと誤差対策は必要 09:42 誤差を発生させてみる 11:14 DBL_EPSILON 12:41 Decimal なら誤差が生じない、こともある 13:23 誤差の幅はどうやって求める? 17:07 誤差判定の匙加減 21:44 誤差を考慮した等価演算子で上書きしてみる 23:16 Decimal 型でも誤差は生まれる 27:03 誤差の範囲を考えるのが難しそう 31:47 範囲演算でも判定できそう 32:40 何を誤差とするかは状況にもよりそう 36:04 クロージング ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #281
結構これは良いかどうか、面白い点があります。例えば、
0.0.1
0.09999
みたいな表現になることがありますが、見た目で
0.1
倍になっていると感じる人もいるかもしれません。これが1つの派生みたいな感じなんですよね。私は全然気にしたことがなかったので、色々試してみようと思います。
C言語以来こういう新しいことを試す機会がなかなか無かったんですよね。浮動小数点数の扱いに関する問題やデシマルナンバーの扱いについても考えてみます。デシマルナンバーを使うと浮動小数点数の誤差を考慮した比較が可能です。
例えば、
2.22e-16
のような小さい値の誤差が出なくなります。NSデシマルナンバーを使うと、2進数ではなく10進数で扱うため、このような誤差が発生しないのですね。ただし、精度の高い比較が必要な場合は別の方法を考える必要があります。
一方で、
A = 1
B = 1
B
R
を足して引いた場合にどうなるのかという議論もありました。例えば、次のようなコードで検証します。
let A = 1.0 let B = 1.0 let R = 0.000000000000001 if A == B { print("A は B に等しい") } else { print("A は B に等しくない") }
このような場合、浮動小数点数の誤差があるため等価でないと判断されることがあります。対策として、絶対値の差をとって比較する方法があります。たとえば:
if abs(A - B) < Double.ulpOfOne { print("A は B に等しい") } else { print("A は B に等しくない") }
このように記述すると、微小な誤差を無視して比較ができます。
NSデシマルナンバーは、有効桁数がダブル型よりも大きいので、精度の高い計算が必要な場合は有利です。具体的には、ダブル型の有効桁数は約15桁ですが、NSデシマルナンバーでは38桁の精度を持ちます。このため、より精度の高い計算が必要な場合にはNSデシマルナンバーの使用が推奨されます。 1より大きい最小の数について考えると、自分はあまり浮動小数点数の計算をしていないことに気付きました。まず、
double
型のイプシロンについて見つけたいと思いましたが、見つかりませんでした。しかし、インポートAIのライブラリを使えば見つかるでしょう。
デシマル(Decimal)で計算を行うと、データに対して符号なしの大きい項を見つけることができます。この方法で行くと確実に結果が得られます。例えば、デシマルナンバーを使うときれいに計算してくれます。
次に教えてもらった数式についてですが、この数式の後ろに出ているものが何なのかを理解できていません。数式では最大値を取るということでした。絶対値を取ったので符号がなくなります。そして、符号を無くした部分の大きい項が残ります。これが 1 と 0.01 を引いて 0.999 のようになるケースです。実際は 0.9 ですが、表現上は 1 になっています。
このように、AとBのうち大きい項を取って、それが 1 以上かどうかを判断します。それが一番小さい単位になります。例として、これが 2 だった場合は 2 になるということです。
左辺が
0.0000
みたいな形になり、右辺が
UAPオブワン
(one)の一方の値を意味しています。ここで重要なのは、AとBの差が小さい値であるかどうかを判定している点です。具体例では、
Y
という0に近い値を出し、これがダブル型で表現可能な最も0に近い値です。この値を許容誤差として用います。
許容誤差を考慮すると制度を低くすることができます。この数式はコメントだけでは完全に応用されていない感じがしますが、差異や誤差を計算量に応じて見積もっていると解釈できます。
誤差を考慮しない範囲までは問題が生じません。例えば、
Eのマイナス16乗
で有効桁数を考えると、
ダブル
型の有効桁数は15桁となります。
Eのマイナス16乗
で15桁を超える誤差が出ると、ズレが目立ってきますが、それ以上では問題が発生しません。
理解するためにもう一度見直すと、例えば絶対値を取るときの
FABS
関数を使う場面が出てきます。比較したり、誤差を計算したりする過程で絶対値を取る操作が必要です。これで誤差を最小限に抑えた計算が可能になります。 なので、企画したいもの同士を絶対値で引いたのが完全値だと思って0になるんですけど、丸め誤差が入ると誤差が出てしまいます。そのため、少し下がっていた部分が最小値以下だったら、それは0と同じように扱われるので、比較したときに同一と判定されるということですね。
ここもそれなりに理由はあるのでしょうけど、最終的に値の引き算ができればいいということですね。期待する結果の検査を実行したときにどうなのかを確認したいんですよね。これで一致するでしょ、こういう解決策もあります。とりあえずそうですね、確かにそうです。
ここで15を引く理由が何かというと、誤差が起こると、これをなくすためにオンラインの同期をするという点があります。4を足して引いたら
ULP of 1
だけを使うのも怖い気がしますけどね。その誤差がどこまで使用されているかがあります。とりあえず、0にするか1にするか、その変数に対して0か1かというところを解決して、このコードを入れるかどうかちょっと迷った感じですね。
0と1にするという観点だと、計算結果が0.00001みたいな値が0と表現されている場合には多分止まりそうですよね。その辺りは結論で良さそうな気がします。デシマルも、00は大丈夫な気がしますね。0じゃない比較の場合、1.0でデシマルが強い部分もあると思いますが、AとBと一致する場合はデシマルのAとデシマルのBで一致しないかもしれません。これは変換できないのか、ファンデーションを使って一致しないのか分からないので、ちょっと試してみないと分からないですが一致してほしいですね。
遅れて結果が出たらどうなるのかという観点で動作を確認したいです。例えば8 - 1.535でもマグニチュード(絶対値の計算)を見たいです。結果が割れてくる場合ですね。保管することに関しても、デシマルの時にはデシマルで貫かないといけなくて、その辺が難しいところです。特に他のAPIが絡んできてダブルに変えないといけない状況が発生すると無駄になってしまいます。そういった欠点があるので、それは注意が必要かなと思います。
初めからデシマルで最後まで表示する部分や対応する部分までデシマルで貫けるなら、計算誤差を出したくなかったらそれが一番いいでしょうね。ダブルを使って一致判定をする場合、その一致判定がどこまで必要かも考える必要があります。不等号でうまく判定するとかも考えられますね。
ネクストダウンとネクストアップに関しては、数字が必ずしもその間にあるとは限らないです。
A
のネクストダウンと
B
も同じように確認したいです。表示の問題でヒントを出せるかどうかも考えつつ、これは自動書数点で表現可能な次の値です。ULP of 1というのがゼロからの一番小さい単位ですが、このノリでAからとなり、なるほどこういうふうに誤差が乗るということですね。 なるほど。だから、こういったこともどのポイントでもUlp(Unit in the Last Place)を持ってきても、ちょっと怖いなと思います。それだと、さっきみたいに少し上乗せしてあげないと誤差を表現しきれないことがあるんじゃないかなという怖さがあります。だから、きっとこの辺が妥当なんですよ。これよりは直近の数字に収まっているかとは限らないですけどね。
あとは、値を引いちゃうから、例えばAとB、この値が極端に大きいとき、一つが10の15乗とかで、もう一方が10のマイナス15乗だったりすると、多分ゼロになっちゃったり、無視されたりすることがあり、それはまた別の問題ですけどね。別の誤差も起こったりすると思います。そうやって色々考えていくと、あまり一致比較はしない方が良いのかなと思います。
ただ、そういったのも踏まえると、どこまで誤差を許容するかという課題が出てくるかもしれません。例えば、「1ドル20セントのものかどうか」を判定したいのであれば、一定の誤差範囲での判定で良いわけです。誤差が0.0001ぐらいでも十分なんじゃないかと思います。
これはダブルで判定していたけど、連続する値でもいけるかもしれません。例えば、
0.0001
であれば良いとか。絶対に1個がゼロってわけでもないので、それで十分ですね。
また、
contains
A-B
の確認値が同じであれば、こういう書き方もできるわけです。例えば、Ulpの最大値を使った評価とか。全体的に誤差は状況によって調整が必要ですね。リコーダーシステムになったら少し違うのかもしれませんが、これはリコーダーではないし、クイズシステムとも異なっているので、統一しても差異が生じるのです。
プログラミングにおいて、計算による誤差が生じるか、最初からのデータ誤差があるので、最初から一致しない場合もあります。例えば、「1÷3×3」でちゃんと1に戻るべきなのに、そうならない場合があります。これは計算の精度の問題で、
1÷3×3
1.9999
になるかと思いきや、ちゃんと1に戻るのです。その辺りを意識して、比較するときに誤差が必要です。
以前、自分も1÷3が元に戻る精度の都合で上手く戻ると感じたことがあります。しかし、割り切れなかった部分で誤差が発生し、それが戻るというのは勝手に戻ってもらっても困るものです。そんな背景から、誤差を考慮する必要があります。特に不動小数点数の計算には誤差が含まれるので、注意が必要です。
スーパーコンピュータなどを扱う場合にこのような誤差の問題が顕著に現れます。しかし、普段から意識したことはあまりないので、面白い話でした。
それでは、今回はここまでにします。今日の内容については過去にも扱ったことがありますので、興味があれば前回の資料も見てみてください。今日はこれで終わりにします。お疲れ様でした。ありがとうございました。