Ruby - 多倍長浮動小数点数の加減算!

Updated:


前回は、C++ による多倍長浮動小数点数同士の加減算について紹介しました。

今回は、同じことを Ruby で試してみました。

0. 前提条件

  • Linux Mint 14 Nadia (64bit) での作業を想定。
  • Ruby 2.0.0-p0 を使用。

1. 考え方

考え方は C++ 版と同じなので、「C++ - 多倍長浮動小数点数の加減算!」を参照のこと。

2. Ruby スクリプト作成

  • 今回、 A - C < 0 (A < C) になることは想定していない。

File: add_big_float.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#! /usr/local/bin/ruby
#*********************************************
# 浮動小数点数加減算
#*********************************************
#
class AddBigFloat
  D_MAX = 1001  # 有効桁数(小数点以下+べき指数)

  def initialize
    # 使用する被加減数・加減数を設定(テストなので適当な乱数を使用)
    #   ( A + B, A - C で A > C となることが条件だが
    #     稀に条件に合致しない値が生成されるかもしれない )
    @a, @b, @c = Array.new, Array.new, Array.new
    @a << rand(D_MAX - 1) + 1      # Aのべき指数部
    @b << rand(D_MAX - 1) + 1      # Bのべき指数部
    @c << @a[0] - rand(@a[0]) - 1  # Cのべき指数部
    (D_MAX - 1).times do |_|
      @a << rand(10)               # Aの小数部
      @b << rand(10)               # Bの小数部
      @c << rand(10)               # Cの小数部
    end
  end

  # 計算
  def calc
    # 計算する被加減数・加数・減数を出力
    puts "A ="; fdisp(@a)
    puts "B ="; fdisp(@b)
    puts "C ="; fdisp(@c)
    # 計算(Z = A + B) ・結果出力
    puts "A + B ="
    fdisp(fadd(@a, @b, 1))
    # 計算(Z = A - C) ・結果出力
    puts "A - C ="
    fdisp(fadd(@a, @c, -1))
  rescue => e
    raise
  end

  private

  # ========================================================
  # 加減算
  # ( 固定長整数同士の加減算, 正規化含む )
  #
  #   [引数]   a   : 被加減数配列
  #            b   : 加減数配列
  #            id  : 1: 加算(A + B(, -1: 減算(A - B)
  #   [返り値] z   : 計算結果配列
  # ========================================================
  def add(a, b, id)
    z = Array.new  # 結果格納配列

    begin
      # 計算
      if id >= 0  # 加算
        a.size.times { |i| z << a[i] + b[i] }
      else        # 減算
        a.size.times { |i| z << a[i] - b[i] }
      end
      # 正規化
      z = norm(z)
      return z
    rescue => e
      raise
    end
  end

  # ========================================================
  # 加減算
  # ( 浮動小数点数同士の加減算, 正規化含む )
  #
  #   [引数]   a  : 被加減数配列
  #            b  : 加減数配列
  #            id : 1: 加算(A + B), -1: 減算(A - B)
  #   [返り値] z  : 出力データ配列(Z = A + B or Z = A - B)
  # ========================================================
  def fadd(a, b, id)
    z = Array.new  # 結果格納配列

    begin
      # 計算
      if a[0] >= b[0]  # A のべき指数 >= B のべき指数
        k = a[0] - b[0]
        z = a[0...(k + 1)]
        z += add(a[(k + 1)..-1], b[1..(a.size - 1 + k)], id)
      else             # A のべき指数 <  B のべき指数
        z << b[0]
        k = b[0] - a[0]
        # 位の異なる部分
        if id >= 0  # 加算
          z += b[1..k]
        else        # 減算
          1.upto(k) { |i| z << -b[i] }
        end
        # 位の同じ部分の加減算
        z += add(a[1..(-k - 1)], b[(k + 1)..-1], id)
      end
      # 正規化
      z = [z[0]] + norm(z[1..-1])  # 固定長整数
      z = fnorm(z)                 # 浮動小数点数
      return z
    rescue => e
      raise
    end
  end

  # ========================================================
  # 正規化
  # ( Fixed Normalize(A) by Base-Value )
  #
  #   [引数]   a : 正規化前データ配列
  #   [返り値] a : 正規化後データ配列
  # ========================================================
  def norm(a)
    cr = 0         # 繰り上がり

    begin
      (a.size - 1).downto(1) do |i|
        cr        = a[i] / 10
        a[i]     -= cr * 10
        a[i - 1] += cr
      end
      (a.size - 1).downto(1) do |i|
        if a[i] < 0
          a[i - 1] -= 1
          a[i]     += 10
        end
      end
      return a
    rescue => e
      raise
    end
  end

  # ========================================================
  # 正規化
  # ( Floating Normalize(A) )
  #
  #   [引数]   a : 正規化前データ配列
  #   [返り値] a : 正規化後データ配列
  # ========================================================
  def fnorm(a)
    k = 0  # 上位の 0 の個数

    begin
      # 小数点以下第1位が桁あふれする場合、右に1桁シフト&べき指数加算
      if a[1] >= 10
        (a.size - 2).downto(2) { |i| a[i + 1] = a[i] }
        a[2] = a[1] % 10
        a[1] = a[1] / 10
        a[0] += 1
      end
      # 正規化前のべき指数を退避
      exp = a[0]
      # 上位の 0 の個数をカウント
      1.upto(a.size) do |i|
        unless a[i] == 0
          k = i - 1
          break
        end
      end
      # 上位の 0 の部分に以降の数字をシフト
      1.upto(a.size - k) { |i| a[i] = a[i + k] }
      # シフト後の下位に 0 をセット
      (a.size - k).upto(a.size - 1) { |i| a[i] = 0 }
      # べき指数セット
      a[0] = exp - k
      return a
    rescue => e
      raise
    end
  end

  # ========================================================
  # 結果出力 (指数表示)
  #
  #   [引数]   s    : 結果出力データ配列
  #   [返り値] なし
  # ========================================================
  def fdisp(s)
    if s[1] < 0
      puts "ANS. < 0\nPlease retry!"
      exit
    end
    print "0."
    # 1行に50桁出力
    1.upto(s.size - 1) do |i|
      print s[i]
      print " "    if i % 10 == 0
      print "\n  " if i % 50 == 0
    end
    # べき指数
    puts s[0] < 0 ? " * 10^(#{s[0]})" : " * 10^#{s[0]}"
    puts
  rescue => e
    raise
  end
end

if __FILE__ == $0
  begin
    # 計算クラスインスタンス化
    obj = AddBigFloat.new
    # 計算
    obj.calc
  rescue => e
    $stderr.puts "[#{e.class}] #{e.message}"
    e.backtrace.each{ |tr| $stderr.puts "\t#{tr}" }
  end
end

3. 実行

まず、実行権限を付与。

$ chmod +x add_big_float.rb

そして、実行。

$ ./add_big_float.rb
A =
0.2783737238 1303344608 9347169684 3282329196 5307669513 
  7488984027 9152724850 2953114516 5239024752 8881375559 
  4776030762 3200933909 2347805946 9027550863 4980016846 
  6437587376 3724863387 8289052079 2546386916 9463209606 
  9430104727 1417817305 6061340308 5886569658 3934807534 
  9022223169 9237629594 8589623932 8572577452 2995044468 
  1434355236 5967148315 0728551183 9326565879 0903552919 
  7193617021 7185307099 3793766010 3818257051 2043669198 
  7660839436 6124646218 0098359769 6767128550 1117072199 
  4079639090 8053259429 4657834539 9199989746 9142523390 
  0927673070 1972138156 9454265642 0547769513 2273878983 
  5658314613 3225502634 8727654863 4352393649 2731691913 
  2857300925 7959917752 5874362423 1196284003 9762780233 
  7490294640 8727767922 5976319116 4839032889 7603116162 
  9036273941 1385721188 1044449806 1580096886 0196620642 
  0616850330 5270598331 7963643161 0714343433 0403405273 
  4531243862 8516049185 0402843189 1629731926 2095630644 
  3475105637 9206505869 5169996557 5174741190 6613444696 
  4361713529 1213966077 1722382995 0672113765 2961341066 
  4279110855 7882795131 3033069262 8216293523 9587644749 
   * 10^473

B =
0.0022590812 3710169960 1089793981 3064983884 8976399063 
  0174741110 7085151138 3069839274 5570373653 4920990302 
  1324232041 2352590657 5877070452 1072953500 2407669857 
  6132879125 2529649102 6269263549 9717346260 0662788373 
  1945326059 4405971259 6353011882 7202514913 1548805895 
  5874073241 7889888186 6570490556 1949198176 0614375744 
  8623094975 0675069190 3405049410 1785796064 3289120677 
  3095849091 2012628735 6174677007 9165521273 3298469487 
  1592269693 2551540460 8420790624 5236765502 2036639189 
  8255945043 2041013035 9744058240 4809179002 9289582357 
  8719417926 8618216275 8595304962 6226968526 4466104488 
  7167567081 5603149454 0633372385 3552837529 8976482766 
  0420057267 1270892710 4581766110 4824554239 7046866365 
  5911003836 6570217932 5684673486 0072372723 3905035754 
  8771140381 0586414934 2960402420 2668236561 2089882061 
  6747293260 9461701944 1168686412 8630504241 5542791687 
  4284354351 7828275583 6839070337 5711975924 4086873321 
  2040262977 9734719019 5731178504 8276668426 2971662580 
  5066388788 2366427242 1882430337 6182753573 8337974385 
  8914788122 5122202068 9952880775 7626255357 6530388836 
   * 10^349

C =
0.0223515925 3368391270 4463511258 6838833759 5344753262 
  9298194326 6849626597 4092467072 1883731160 2936293254 
  9591015466 2862138558 7921550146 9830278274 7052198091 
  3162472250 5165091329 3265422066 8905699592 7889242878 
  1589146326 4595601809 5825637063 8874329409 5548438178 
  7824023837 6842833089 0563265810 5163775672 4359175871 
  7523234617 0939926181 4347155887 0876924642 6704926060 
  4752210118 4714967153 2454327713 5988714622 6605249346 
  9447198350 6644704623 1670956736 6329244574 4852326315 
  1782508575 2386060636 4910666220 6204822996 5681950263 
  0784586598 4507427723 1537926607 8576097458 7197469594 
  4129706598 5813054378 3653792810 3124948815 8397367015 
  3030694408 6362597428 1053575333 6750544320 4213368161 
  0932897812 6921253272 7919690724 6679955380 2214087007 
  7801741483 1836299808 9203585085 2352469586 0418839310 
  4977156521 7471613380 8878610577 3104178078 3844178350 
  8862935375 6050332904 3349661461 0753487181 5552636745 
  6878809641 7832467427 3930317837 8647324176 7295023847 
  8564697338 5348993215 3433521128 8695055944 3107985729 
  3300541411 4102409842 5813750909 6607397015 7712412612 
   * 10^448

A + B =
0.2783737238 1303344608 9347169684 3282329196 5307669513 
  7488984027 9152724850 2953114516 5239024752 8881375559 
  4776030762 3200933909 2347808205 9839921880 4940125826 
  0418893874 7609761027 7352069553 3657095432 0601516590 
  8704661764 5071309404 6363472731 7927804917 4592395241 
  9474330465 2737870361 8447237220 7697830417 2097671394 
  4984326971 2227214593 9101745716 5386006476 2163188220 
  9076337273 2098461979 9689353417 7060046040 0230326247 
  8217034356 4300707655 5843222079 1742196057 0307412704 
  3489817670 4117588341 5335144124 8291191009 7878140857 
  7935589622 3245468003 8941424869 0241024667 2734721062 
  6282838289 8727706298 7917480457 9395597750 5767666319 
  1097781843 6962846710 8232234364 9123145825 6038639764 
  2452917337 7254214533 0465035873 1920593204 7057179500 
  1421629224 8915618836 3810491811 8847223975 2907078818 
  6727332785 9510303018 4329234261 4551000454 8335973740 
  8017251100 1239439688 6157720303 2010790567 7029926684 
  5895372461 5767714857 7231671286 8435687360 8557561565 
  0774576579 5455520356 3409811430 5023896592 8545024973 
  4616682053 3807203818 6354273289 1194266995 8607217866 
   * 10^473

A - C =
0.2783737238 1303344608 9347167449 1689795512 6180624878 
  6363115639 5776771402 7626821534 5806356256 6221634634 
  8068811925 0084904546 3022310036 7480922242 1124137631 
  1422889073 5897392865 8479920454 5321335266 0330276952 
  7223415670 1458538413 1773524417 1253923702 3753849278 
  5315834426 6296674110 4771745692 6188809023 9686138835 
  4853303598 8399904723 3141375951 5864856479 8285409448 
  1604908252 4721040050 1187718488 2806409901 5328344655 
  4889479549 4662380165 5163665297 6932062103 0654755489 
  8405975798 3595810906 2026316714 8342465886 3078874283 
  4305611021 9672481337 4427957796 1887924438 9501563604 
  2997528852 3479630660 1768213566 3692535518 7293855375 
  3576269676 3078333778 9172832116 1755420378 0019969697 
  9956927135 4295725788 9160209787 5057763677 2275836965 
  9963807141 5847699047 2343671788 7431778523 0215728606 
  2108326805 8311994143 4032593389 5062168716 9065316487 
  3473512821 0708210743 2567754559 8092171422 8805197147 
  7328998103 0488350343 1495427769 4210562865 9870705393 
  2577927055 8796293126 9337597348 0938260275 3639806731 
  2166223905 2288364051 4460136257 4075152499 8603386612 
   * 10^473

桁を合わせて加減算してみると、結果が合っていることが分かる。


簡単な内容ですが、多倍長演算を行う際に必要になることもあるでしょう。

以上。





 

Sponsored Link

 

Comments