LoginSignup
1
0

More than 5 years have passed since last update.

VBAHaskellの紹介 その20(有理数の計算)

Last updated at Posted at 2015-07-05

 とあるブログ記事でVBA(というかコンピュータ一般)で小数演算が正確にできないことについて書かれているのを読んだ。
 表したい数値が無理数だと話は別だが、分数ならば有理数をあらわす型を作って数として表現し、正確な計算ができるように演算を定義する、というのはよくある。そこでVBAでも有理数を扱うサンプル的なプログラムを作ってみた。あえてデータ構造をクラス化せず、以下のような単純な配列で分子と分母を保持して、関数主体で実装した。
 分子 → 配列の最初の要素
 分母 → 配列の2番目の要素
 符号 → 分子に付ける
 分母と分子は互いに素なLong値(make_ratio関数で作成した場合)
 (LongLongにしてもいいけど・・・)

'有理数の生成
Function make_ratio(ByRef a As Variant, ByRef b As Variant) As Variant
    Dim gcd As Long:    gcd = getGcd(a, b)
    make_ratio = VBA.Array(Sgn(a * b) * (Abs(a) \ gcd), Abs(b) \ gcd)
End Function

    '最大公約数
    Public Function getGcd(ByVal a As Long, ByVal b As Long) As Long
        If a = 0 Then
            getGcd = 1
        ElseIf b = 0 Then
            getGcd = Abs(a)
        Else
            getGcd = getGcd(b, Abs(a) Mod Abs(b))
        End If
    End Function

make_ratio を含め、以下の関数を作った。1

関数 内容
make_ratio 有理数の生成
ratio2double Doubleに変換
ratio2str 文字列に変換
ratio_plus 有理数の加算
ratio_negate 有理数の符号変更
ratio_minus 有理数の減算
ratio_mult 有理数の乗算
ratio_pow 有理数のベキ乗
ratio_divide 有理数の除算
ratio_sgn 有理数の符号
ratio_abs 有理数の絶対値
ratio_equal 有理数の比較 (a = b)
ratio_not_equal 有理数の比較 (a <> b)
ratio_less 有理数の比較 (a < b)
ratio_less_equal 有理数の比較 (a <= b)
ratio_greater 有理数の比較 (a > b)
ratio_greater_equal 有理数の比較 (a >= b)

元の記事では、1から0.001を千回引いても0にならないことから話が始まっていたので、それがどうなるか確認してみる。

r1=make_ratio(1,1)       ' 1/1
r2=make_ratio(1, 1000)   ' 1/1000
' 1から1/1000を1000回引いた結果
printM repeat_while(r1, p_true, p_ratio_minus(,r2), 1000)
  0  1000                ' 0/1000
' 1から1/1000を1003回引いた過程を配列に入れる
m = generate_while(r1, p_true, p_ratio_minus(,r2), 1003)
' その各要素をDoubleに変換する
d = mapF(p_ratio2double, m)
' 先頭6要素を確認
printM d,6
  1  0.999  0.998  0.997  0.996  0.995
' 末尾6要素を確認
printM d,-6
  0.002  0.001  0  -0.001  -0.002  -0.003

問題はなさそうだ。
比較演算があるのでソートもできる。2

' 分子の列(-20~20 のランダム整数10個)
nums = mapF(p_getCLng,mapF(p_rnd(,20), repeat(-20, 10)))
' 分母の列(1~20 のランダム整数10個)
dens = mapF(p_getCLng,mapF(p_rnd(,20), repeat(1, 10)))
' zipWithに分子の列、分母の列を与えて有理数列を作れる
m = zipWith(p_make_ratio, nums, dens)
' 表示してみる(ratio2str関数で文字列化)
printM mapF(p_ratio2str, m)
  4/3  -5/7  11/18  -5/17  -8/1  17/11  5/18  5/9  -3/14  -16/11
' 比較演算として ratio_less を使ってソート
m2 = subV(m, sortIndex_pred(m, p_ratio_less))  
' ソート後の有理数列を表示してみる
printM mapF(p_ratio2str, m2)
  -8/1  -16/11  -5/7  -5/17  -3/14  5/18  5/9  11/18  4/3  17/11

正確性はいいのだが、以下の点で不満だ。
・遅い(計算のたびに通分計算が入る)
・オーバーフローが心配(ベキ乗とかで問題になりそう。LongLongにしても同じことだ。)
・Haskell関係ない()


VBAHaskellの紹介 その19(ParamArrayとswapVariant)
VBAHaskellの紹介 その1(最初はmapF)
ソース https://github.com/mYmd/VBA

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0