M.Hiroi's Home Page

Functional Programming

Yet Another Haskell Problems

[ PrevPage | Haskell | NextPage ]

●問題81

Word32 (無符号 32 bit 整数) とリストを使ってシンプルな無符号多倍長整数 (Bignum) を実装します。基数を 65536 とし、リストの先頭が下位の桁、末尾を上位の桁とします。簡単な例を示します。

0 => [0]
1 => [1]
65535 => [0xFFFF]
65536 => [0,1]
4294967295 => [0xFFFF,0xFFFF]
4294967296 => [0,0,1]

整数 (Integer) を多倍長整数に変換する関数 toBignum n を定義してください。

type Bignum = [Word32]
toBignum :: Integer -> Bignum
*Main> toBignum 0
[0]
*Main> toBignum 1
[1]
*Main> toBignum 65535
[65535]
*Main> toBignum 65536
[0,1]
*Main> toBignum 0xffffffff
[65535,65535]
*Main> toBignum 0x100000000
[0,0,1]

解答

●問題82

多倍長整数を整数 (Integer) に変換する関数 bignumToInteger xs を定義してください。

bignumToInteger :: Bignum -> Integer
*Main> bignumToInteger [0]
0
*Main> bignumToInteger [1]
1
*Main> bignumToInteger [65535]
65535
*Main> bignumToInteger [0,1]
65536
*Main> bignumToInteger [0,0,1]
4294967296

解答

●問題83

2 つの多倍長整数 xs, ys の論理積を求める関数 bignumAND xs ys を定義してください。

bignumAND :: Bignum -> Bignum -> Bignum
*Main> bignumAND [1,1,1] [0]
[0]
*Main> bignumAND [1,1,1] [1]
[1]
*Main> bignumAND [1,2,1] [3,3,3]
[1,2,1]
*Main> bignumAND [1,2,1] [3,3,2]
[1,2]
*Main> bignumAND [1,2,1] [3,0,2]
[1]

解答

●問題84

2 つの多倍長整数 xs, ys の論理和を求める関数 bignumOR xs ys を定義してください。

bignumOR :: Bignum -> Bignum -> Bignum
*Main> bignumOR [1,1,1] [0]
[1,1,1]
*Main> bignumOR [1,1,1] [2,2,2]
[3,3,3]
*Main> bignumOR [1,1,1] [2,2,2,2,2,2]
[3,3,3,2,2,2]

解答

●問題85

2 つの多倍長整数 xs, ys の排他的論理和を求める関数 bignumXOR xs ys を定義してください。

bignumXOR :: Bignum -> Bignum -> Bignum
*Main> bignumXOR [0,1,2,3] [0]
[0,1,2,3]
*Main> bignumXOR [0,1,2,3] [1]
[1,1,2,3]
*Main> bignumXOR [0,1,2,3] [1,2,3,4]
[1,3,1,7]
*Main> bignumXOR [0,1,2,3] [0,1,2,3]
[0]

解答

●問題86

多倍長整数 xs を左へ n bit シフトする関数 bignumShiftLeft xs n を定義してください。

bignumShiftLeft :: Bignum -> Int -> Bignum
*Main> bignumShiftLeft [1] 0
[1]
*Main> bignumShiftLeft [1] 1
[2]
*Main> bignumShiftLeft [1,1,1,1] 15
[32768,32768,32768,32768]
*Main> bignumShiftLeft [1,1,1,1] 16
[0,1,1,1,1]
*Main> bignumShiftLeft [1,1,1,1] 32
[0,0,1,1,1,1]

解答

●問題87

多倍長整数 xs を右へ n bit シフトする関数 bignumShiftRight xs n を定義してください。

bignumShiftRight :: Bignum -> Int -> Bignum
*Main> bignumShiftRight [0,1] 0
[0,1]
*Main> bignumShiftRight [0,1] 1
[32768]
*Main> bignumShiftRight [0,1] 15
[2]
*Main> bignumShiftRight [0,1] 16
[1]
*Main> bignumShiftRight [0,1] 17
[0]
*Main> bignumShiftRight [0,0,1] 32
[1]

解答

●問題88

多倍長整数を比較する関数 bignumEQ, bignumNE, bignumGT, bignumGE, bignumLT, bignumLE を定義してください。

bignumEQ :: Bignum -> Bignum -> Bool  -- ==
bignumNE :: Bignum -> Bignum -> Bool  -- /=
bignumGT :: Bignum -> Bignum -> Bool  -- >
bignumGE :: Bignum -> Bignum -> Bool  -- >=
bignumLT :: Bignum -> Bignum -> Bool  -- <
bignumLE :: Bignum -> Bignum -> Bool  -- <=
*Main> bignumEQ [1,1,1] [1,1,1]
True
*Main> bignumEQ [1,1,1] [1,0,1]
False
*Main> bignumNE [1,1,1] [1,1,1]
False
*Main> bignumNE [1,1,1] [0,1,1]
True
*Main> bignumGT [0,0,1] [0,1]
True
*Main> bignumGT [0,0,1] [0,0,2]
False
*Main> bignumGE [0,0,1] [0,0,1]
True
*Main> bignumGE [0,0,2] [0,0,1]
True
*Main> bignumGE [0,0,1] [0,0,2]
False
*Main> bignumLT [0,0,2] [0,0,1]
False
*Main> bignumLT [0,2] [0,0,1]
True
*Main> bignumLE [0,0,1] [0,0,1]
True
*Main> bignumLE [0,0,1] [0,0,2]
True
*Main> bignumLE [0,0,1] [0,2]
False

解答

●問題89

多倍長整数 xs と基数未満の整数 n を加算する関数 bignumAddInt xs n を定義してください。

bignumAddInt :: Bignum -> Word32 -> Bignum
*Main> bignumAddInt [65535,65535] 0
[65535,65535]
*Main> bignumAddInt [65535,65535] 1
[0,0,1]
*Main> bignumAddInt [65535,65535] 65535
[65534,0,1]

解答

●問題90

多倍長整数 xs. ys を加算する関数 bignumAdd xs ys を定義してください。

bignumAdd :: Bignum -> Bignum -> Bignum
*Main> bignumAdd [0,65535] [65535,1]
[65535,0,1]
*Main> bignumAdd [65535,65535] [65535,65535]
[65534,65535,1]
*Main> bignumAdd [65535,65535] [1,0,1]
[0,0,2]

解答

●問題91

多倍長整数 xs と基数未満の整数 n を減算する関数 bignumSubInt xs n を定義してください。

bignumSubInt :: Bignum -> Word32 -> Bignum
*Main> bignumSubInt [1] 0
[1]
*Main> bignumSubInt [1] 1
[0]
*Main> bignumSubInt [1] 2
[65535*** Exception: bignum: underflow
*Main> bignumSubInt [0,1] 1
[65535]
*Main> bignumSubInt [0,0,0,0,1] 1
[65535,65535,65535,65535]
*Main> bignumSubInt [0,0,0,0,1] 65535
[1,65535,65535,65535]

解答

●問題92

多倍長整数 xs. ys を減算する関数 bignumSub xs ys を定義してください。

bignumSub :: Bignum -> Bignum -> Bignum
*Main> bignumSub [0,0,1] [65535,65535]
[1]
*Main> bignumSub [0,0,1] [0,0,1]
[0]
*Main> bignumSub [0,0,1] [0,1]
[0,65535]
*Main> bignumSub [0,0,1] [1,0,1]
[65535,65535,65535*** Exception: bignum: underflow

解答

●問題93

多倍長整数 xs と基数未満の整数 n を乗算する関数 bignumMulInt xs n を定義してください。

bignumMulInt :: Bignum -> Word32 -> Bignum
*Main> bignumMulInt [1,2,3,4,5] 0
[0]
*Main> bignumMulInt [1,2,3,4,5] 1
[1,2,3,4,5]
*Main> bignumMulInt [1,2,3,4,5] 2
[2,4,6,8,10]
*Main> bignumMulInt [65535, 65535] 65535
[1,65535,65534]

解答

●問題94

多倍長整数 xs. ys を乗算する関数 bignumMul xs ys を定義してください。

bignumMul :: Bignum -> Bignum -> Bignum
*Main> bignumMul [1,1,1] [1,1,1]
[1,2,3,2,1]
*Main> bignumMul [65535,65535,65535] [1,1,1]
[65535,65534,65534,0,1,1]
*Main> bignumMul [65535,65535,65535] [65535,65535,65535]
[1,0,0,65534,65535,65535]

解答

●問題95

多倍長整数 xs と基数未満の整数 n を除算する関数 bignumDivInt xs n を定義してください。返り値は商と剰余をタプルで返すものとします。

bignumDivInt :: Bignum -> Word32 -> (Bignum, Bignum)
*Main> bignumDivInt [2,4,6,8,10] 1
([2,4,6,8,10],[0])
*Main> bignumDivInt [2,4,6,8,10] 2
([1,2,3,4,5],[0])
*Main> bignumDivInt [2,4,6,8,10] 3
([21846,1,2,21848,3],[0])
*Main> bignumDivInt [2,4,6,8,10] 4
([0,32769,1,32770,2],[2])
*Main> bignumDivInt [2,4,6,8,10] 65535
([28,24,18,10],[30])
*Main> bignumDivInt [2,4,6,8,10] 0
*** Exception: bignum: divide by zero

解答

●問題96

多倍長整数 xs. ys を除算する関数 bignumDiv xs ys を定義してください。返り値は商と剰余をタプルに格納して返すものとします。

bignumDiv :: Bignum -> Bignum -> (Bignum, Bignum)
*Main> bignumDiv [0,0,0,1] [0,0,0,1]
([1],[0])
*Main> bignumDiv [0,0,0,1] [0,0,1]
([0,1],[0])
*Main> bignumDiv [1,0,0,1] [0,0,1]
([0,1],[1])
*Main> bignumDiv [0,0,0,1] [65535,65535]
([0,1],[0,1])

解答

●問題97

多倍長整数 xs を文字列に変換する関数 bignumToString xs r を定義してください。r は基数を表す整数値 (r <= 16) です。

bignumToString :: Bignum -> Int -> String
*Main> bignumToString [0,0,0,1] 10
"281474976710656"
*Main> bignumToString [0,0,0,1] 16
"1000000000000"
*Main> bignumToString [65535,65535,65535] 10
"281474976710655"
*Main> bignumToString [65535,65535,65535] 16
"FFFFFFFFFFFF"

解答

●問題98

文字列 str を多倍長整数に変換する関数 stringToBignum str r を定義してください。r は基数を表す整数値 (r <= 16) です。

stringToBignum :: String -> Int -> Bignum
*Main> stringToBignum "1234567890" 10
[722,18838]
*Main> stringToBignum "FFFFFFFFFFFFFFFF" 16
[65535,65535,65535,65535]
*Main> stringToBignum "1234567890ABCDEF" 16
[52719,37035,22136,4660]
*Main> stringToBignum "12345678901234567890" 10
[2770,60191,43404,43860]

解答

●問題99

多倍長整数の平方根の整数部分を求める関数 bignumSqrt xs を定義してください。

bignumSqrt :: bignum -> bignum
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 2 * 10^20
14142135623
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 2 * 10^40
141421356237309504880
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 2 * 10^80
14142135623730950488016887242096980785696
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 3 * 10^20
17320508075
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 3 * 10^40
173205080756887729352
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 3 * 10^80
17320508075688772935274463415058723669428

解答


●解答81

リスト : 整数を多倍長整数に変換する

base :: Word32
base = 65536

baseL :: Integer
baseL = 65536

toBignum :: Integer -> Bignum
toBignum x
  | x < 0     = error "toBignum : domain error"
  | x < baseL = [fromIntegral x]
  | otherwise = fromIntegral (x `mod` baseL) : toBignum (x `div` baseL)

toBignum は簡単です。x が baseL よりも小さければ x を Wrod32 に変換してからリストに格納して返します。あとは、整数 n を基数 baseL で割り算していき、剰余を toBignum の返り値 (リスト) に追加していくだけです。

●解答82

リスト : 多倍長整数を整数に変換する

bignumToInteger :: Bignum -> Integer
bignumToInteger xs =
  foldr (\x a -> a * baseL + fromIntegral x) 0 xs

bignumToInteger も簡単です。引数 xs を foldr で末尾から畳み込むだけです。

●解答83

リスト : 多倍長整数の論理積

bignumAND :: Bignum -> Bignum -> Bignum
bignumAND xs ys = if null zs then [0] else zs
  where zs = dropWhileEnd (== 0) $ zipWith (.&.) xs ys

bignumAND は xs と ys の要素の論理積を zipWith で求め、末尾から連続する 0 を dropWhileEnd で取り除きます。その結果、zs が空リストになったならば [0] を返します。そうでなければ zs をそのまま返します。

●解答84

リスト : 多倍長整数の論理和

bignumLogical :: (Word32 -> Word32 -> Word32) -> Bignum -> Bignum -> Bignum
bignumLogical _ []     []     = []
bignumLogical f xs     []     = xs
bignumLogical f []     ys     = ys
bignumLogical f (x:xs) (y:ys) = f x y : bignumLogical f xs ys

bignumOR :: Bignum -> Bignum -> Bignum
bignumOR = bignumLogical (.|.)

bignumOR と bignumXOR の処理は関数 bignumLogical で行います。bignumLogical は xs と ys の要素を順番に取り出して関数 f を適用し、その結果を bignumLogical の返り値 (リスト) に追加します。ys が空リストになった場合は xs を返し、xs が空リストになった場合は ys を返します。両方とも空リストの場合は空リストを返します。

●解答85

リスト : 多倍長整数の排他的論理和

bignumXOR :: Bignum -> Bignum -> Bignum
bignumXOR xs ys = if null zs then [0] else zs
  where zs = dropWhileEnd (==0) $ bignumLogical xor xs ys

bignumXOR は bignumLogical を呼び出して、リストの要素を排他的論理和を求め、dropWhileEnd で末尾から連続する 0 を取り除きます。その結果、zs が空リストになったら [0] を返します。そうでなければ zs をそのまま返します。

●解答86

リスト : 多倍長整数の左シフト

baseBit :: Int
baseBit = 16

mask :: Word32
mask = 0xffff

bignumShiftLeft16 :: Bignum -> Int -> Bignum
bignumShiftLeft16 xs b = iter xs 0
  where iter []     c = if c == 0 then [] else [c]
        iter (x:xs) c = z : iter xs (shiftR y baseBit')
          where y = shiftL x b
                z = (y .|. c) .&. mask

bignumShiftLeft :: Bignum -> Int -> Bignum
bignumShiftLeft xs n
  | n == 0 || xs == [0] = xs
  | n < baseBit = bignumShiftLeft16 xs n
  | otherwise   = replicate a 0 ++ bignumShiftLeft16 xs b
      where a = n `div` baseBit
            b = n `mod` baseBit

bignumShiftLeft は引数 n が 0 または xs が [0] の場合は xs をそのまま返します。baseBit 未満の場合は関数 bignumShiftLeft16 を呼び出します。そうでなければ、n を baseBit で除算し、商を a に、剰余を b にセットします。そして、xs を b ビットシフトした結果に a 個の 0 を先頭に追加します。

実際のビットシフトは bignumShiftLeft16 で行います。局所関数 iter でリストの要素を順番に取り出します。引数 c には左ビットシフトしたときに溢れたビットをセットします。x を b ビット左シフトした値を y にセットし、y と c の論理和を求め、それと mask の論理積の結果を z にセットします。この z を iter の返り値 (リスト) に追加します。溢れたビットは y を右へ 16 ビットシフトすれば求めることができます。

●解答87

リスト : 多倍長整数の右シフト

bignumShiftRight16 :: Bignum -> Int -> Bignum
bignumShiftRight16 xs b = if null zs then [0] else zs
  where zs = dropWhileEnd (==0) $ snd $ iter xs
        mask = shiftL 1 b - 1
        iter [x]    = (x .&. mask, [shiftR x b])
        iter (x:xs) = let (c, ys) = iter xs
                          a = (shiftR x b) .|. (shiftL c (baseBit - b))
                      in (x .&. mask, a:ys)

bignumShiftRight :: Bignum -> Int -> Bignum
bignumShiftRight xs n
  | n == 0 || xs == [0] = xs
  | n < baseBit = bignumShiftRight16 xs n
  | otherwise   = let ys = drop (n `div` baseBit) xs
                      b  = n `mod` baseBit
                  in if null ys
                     then [0]
                     else bignumShiftRight16 ys b

bignumShiftRight は引数 n が 0 または xs が [0] の場合は xs をそのまま返します。baseBit 未満の場合は関数 bignumShiftRight16 を呼び出します。そうでなければ、n を base_bit で除算し、xs の先頭から商の数だけ要素を取り除き、それを ys にセットします。剰余は b にセットします。もし ys が空リストならば [0] を返します。そうでなければ、bignumShiftRigth16 を呼び出して、ys を b ビット右へシフトします。

実際のビットシフトは関数 bignumShiftRight16 で行います。局所関数 iter で xs の要素を順番に取り出します。最初に iter を再帰呼び出しして、溢れたビット c とシフトした結果 ys を求めます。それから、x を右に b ビットシフトし、それと c を左へ baseBit - b ビットシフトした値の論理和を求めます。この値 a を ys に追加し、溢れるビットを x と mask の論理積で求めます。mask の値は 1 を b ビット左シフトした値から 1 を引き算するだけです。これで下位 b ビットを 1 にすることができます。

●解答88

リスト : 多倍長整数の比較

bignumCompare :: Bignum -> Bignum -> Ordering
bignumCompare [] [] = EQ
bignumCompare [] _  = LT
bignumCompare _  [] = GT
bignumCompare (x:xs) (y:ys) =
  case bignumCompare xs ys of
    EQ -> compare x y
    r  -> r

bignumEQ, bignumNE, bignumGT, bignumLT, bignumGE, bignumLE :: Bignum -> Bignum -> Bool
bignumEQ xs ys = bignumCompare xs ys == EQ
bignumNE xs ys = bignumCompare xs ys /= EQ
bignumGT xs ys = bignumCompare xs ys == GT
bignumLT xs ys = bignumCompare xs ys == LT
bignumGE xs ys = bignumCompare xs ys >= EQ
bignumLE xs ys = bignumCompare xs ys <= EQ

関数 bignumCompare は xs と ys が等しい場合は EQ を、xs のほうが大きい場合は GT を、xs のほうが小さい場合は LT を返します。

bignumCompare は 2 つのリストを順番にたどっていき、xs が先に空リストになったら LT を、ys が先に空リストになったら GT を返します。両方とも空リストになった場合は EQ を返します。bignumCompare を再帰呼び出しして、その結果が EQ の場合は関数 compare で x と y を比較します。そうでなければ、返り値 r をそのまま返します。あとは、各関数から bignumCompare を呼び出して結果を比較するだけです。

●解答89

リスト : 多倍長整数と整数の加算

integerAdd :: Word32 -> Word32 -> Word32 -> (Word32, Word32)
integerAdd x y c =
  let n = x + y + c
  in if n < base then (n, 0) else (n - base, 1)

bignumAddInt :: Bignum -> Word32 -> Bignum
bignumAddInt [] n = if n == 0 then [] else [n]
bignumAddInt (x:xs) n = y : bignumAddInt xs c
  where (y, c) = integerAdd x 0 n

bignumAddInt は最下位の桁と引数 c を加算し、桁上がりがあればそれを上位の桁に加算します。あとは、桁上げの処理を繰り返すだけです。整数同士の加算は関数 integerAdd で行います。引数 x, y, c を加算し、その値 n が base 未満であれば n と 0 をタプルで返します。そうでなければ、n - base と 1 をタプルで返します。

●解答90

リスト : 多倍長整数の加算

bignumAdd :: Bignum -> Bignum -> Bignum
bignumAdd xs ys = iter xs ys 0
  where iter [] ys c = bignumAddInt ys c
        iter xs [] c = bignumAddInt xs c
        iter (x:xs) (y:ys) c =
          let (n, c') = integerAdd x y c
          in n : iter xs ys c'

bignumAdd の処理は局所関数 iter で行います。第 3 引数 c は桁上がりを表します。xs または ys が空リストの場合は bignumAddInt を呼び出して、もう一方の値と c を加算します。そうでなければ、xs と ys の要素と c を integerAdd で加算します。あとは、iter に c' を渡してを再帰呼び出しし、その返り値に n を追加するだけです。

●解答91

リスト : 多倍長整数と整数の減算

integerSub :: Word32 -> Word32 -> Word32 -> (Word32, Word32)
integerSub x y c =
  let n = base + x - y - c
  in if n < base then (n, 1) else (n - base, 0)

bignumSubInt :: Bignum -> Word32 -> Bignum
bignumSubInt xs n = if null zs then [0] else zs
  where zs = dropWhileEnd (== 0) $ iter xs n
        iter [] n =
          if n > 0 then error "bignum: underflow" else []
        iter (x:xs) n =
          let (y, c) = integerSub x 0 n
          in y : iter xs c

bignumSubInt は最下位の桁と引数 n を減算し、桁借りがあればそれを上位の桁から減算します。あとは、桁借りの処理を繰り返すだけです。実際の処理は局所関数 iter で行います。引数が空リストで、桁借りの値 n が正であれば、計算結果は負になるのでエラーを送出します。そうでなければ、空リストを返します。あとは、integerSub で x - n を計算し、桁借り c を iter に渡して再帰呼び出しし、その返り値に y を追加するだけです。最後に、末尾から連続する 0 を dropWhileEnd で取り除き、その結果 zs が空リストであれば [0] を、そうでなければ zs を返します。

integerSub は base + x から y と c を減算して変数 n にセットします。もし、n が base よりも小さい場合、n と桁借りを表す 1 をタプルで返します。そうでなければ、n - base と 0 をタプルで返します。

●解答92

リスト : 多倍長整数の減算

bignumSub :: Bignum -> Bignum -> Bignum
bignumSub xs ys = if null zs then [0] else zs
  where zs = dropWhileEnd (==0) $ iter xs ys 0
        iter [] ys c = if null ys && c == 0
                       then []
                       else error "bignum: underflow"
        iter xs [] c = bignumSubInt xs c
        iter (x:xs) (y:ys) c =
          let (n, c') = integerSub x y c
          in n : iter xs ys c'

bignumSub の処理は局所関数 iter で行います。iter は xs と ys の要素と桁借りを表す引数 c を integerSub で減算し、その結果を iter の返り値 (リスト) に格納していきます。xs が空リストの場合、ys が空リストで c が 0 であれば空リストを返します。そうでなければ、結果は負になるのでエラーを送出します。ys が空リストで xs が空リストでない場合、bignumSubInt で xs から c を減算します。最後に、dropWhileEnd で末尾から連続する 0 を取り除き、その結果 zs が空リストであれば [0] を、そうでなければ zs を返します。

●解答93

リスト : 多倍長整数と整数の乗算

integerMul :: Word32 -> Word32 -> Word32 -> (Word32, Word32)
integerMul x y c =
  let n = x * y + c
  in if n < base
     then (n, 0)
     else (n `mod` base, n `div` base)

bignumMulInt :: Bignum -> Word32 -> Bignum
bignumMulInt xs n
  | n == 0    = [0]
  | n == 1    = xs
  | otherwise = iter xs 0
      where iter [] c =
              if c == 0 then [] else [c]
            iter (x:xs) c = let (y, c') = integerMul x n c
                            in y : iter xs c'

bignumMulInt は引数 n が 0 ならば [0] を、1 ならば xs をそのまま返します。それ以外の場合、最下位の桁から順番に x と掛け算して、iter の返り値 (リスト) に追加します。このとき、桁上がり c' を iter に渡して、上位の桁に足し算します。整数の乗算は関数 integerMul で行います。引数 x, y が乗算する整数、c が桁上がりで加算する値です。x * y + c を n にセットし、値が base 未満であれば n と 0 をタプルで返します。そうでなければ、n と base の剰余と商をタプルで返します。

●解答94

リスト : 多倍長整数の乗算

bignumMul :: Bignum -> Bignum -> Bignum
bignumMul [x] ys = bignumMulInt ys x
bignumMul xs [y] = bignumMulInt xs y
bignumMul xs ys  = iter xs ys [0]
  where iter _  []     a = a
        iter xs (y:ys) a =
          iter (0:xs) ys (bignumAdd (bignumMulInt xs y) a)

多倍長整数同士の乗算は筆算と同じ方法で行います。簡単な例を示しましょう。

xs : (4 3 2 1)
ys : (7 6 5)

        1   2   3   4
*           5   6   7
----------------------
        7  14  21  28
    6  12  18  24   0
5  10  15  20   0   0
----------------------
5  16  34  52  45  28

図 : 多倍長整数の乗算

上図のように、xs を 16 bit 左シフトしながら ys の要素を掛け算し、その値を加算していけばいいわけです。

bignumMul は引数 xs, ys が base 未満の整数であれば、bignumMulInt を呼び出して計算します。そうでなければ、xs と y の乗算を bignumMulInt で求め、累積変数 a にその値を bignumAdd で加算します。ys の次の要素を乗算するとき、xs の先頭に 0 を挿入して 16 bit 左シフトします。

なお、今回の方法は桁数が多くなると遅くなります。これよりも高速な方法として「Karatsuba 法」や「高速フーリエ変換」を使った方法があります。これらのアルゴリズムについては、Fussy さん乗算処理の高速化, 高速フーリエ変換M.Kamada さん離散フーリエ変換を用いた多倍長乗算の話 が参考になると思います。

●解答95

リスト : 多倍長整数と整数の除算

integerDiv :: Word32 -> Word32 -> Word32 -> (Word32, Word32)
integerDiv x y c =
  let n = c * base + x
  in (n `div` y, n `mod` y)

bignumDivInt :: Bignum -> Word32 -> (Bignum, Bignum)
bignumDivInt xs 0 = error "bignum: divide by zero"
bignumDivInt xs 1 = (xs, [0])
bignumDivInt xs n = iter (reverse xs) 0 []
  where iter [] c [] = ([0], [c])
        iter [] c a  = (a, [c])
        iter (x:xs) c a =
          let (p, q) = integerDiv x n c
          in iter xs q (if p == 0 && null a then a else p:a)

bignumDivInt は引数 x が 0 の場合はエラーを送出し、1 の場合は xs と [0] をタプルで返します。それ以外の場合は、xs の上位の桁から順番に整数 x で除算していきます。このため、xs を reverse で反転しています。局所関数 iter の変数 c には上位の桁の余りをセットします。あとは、関数 integerDiv で xs の要素と n の除算を行います。このとき、c * base を加えてから x で割ることに注意してください。あとは商と剰余をタプルで返します。

bignumDivInt は上位の桁から処理を行うため、リストの末尾に 0 が付加されないように工夫する必要があります。商 p が 0 で、かつ累積変数 a が空リストの場合、p を a に追加しません。それ以外の場合は p を a に追加します。最後に、a が空リストであれば zero と [c] を、そうでなければ a と [c] をタプルで返します。

●解答96

多倍長整数の除算は筆算と同じ方法で行いますが、かなり複雑な処理になります。ここではアルゴリズムの概略を説明するだけにとどめます。詳細は 参考文献 をお読みください。

リスト : 多倍長整数の除算 (擬似コード)

xs = (x1 x2 ... xn), ys = (y1 y2 ... ym) とし、xs / ys の商と剰余を求める

*base* / 2 <= ym * d < *base* を満たす d を求め、(xs * d) / (ys * d) を計算する

xs1 = xs * d とする
xs1 と同じ桁数になるよう (ys * d) の下位に 0 を追加たものを ys1 とする
このとき、追加した 0 の個数を s とする

qs = ()
while( s >= 0 ){
  xs1 / ys1 の仮の商 q' を求める。
    (1) xs1 が ys1 よりも少ない桁数の場合、q' は 0 である
    (2) xs1 と ys1 の桁数 (n) が同じ場合、q' = xn / yn とする
    (3) xs1 が n 桁, ys1 が n - 1 桁の場合、q' = min( (xn * base + xn-1) / yn-1, base - 1 ) とする

  if( q' > 0 ){
    ys2 = ys1 * q'
    while( xs1 < ys2 ){
      q' = q' - 1
      ys2 = ys2 - ys1
    }
    xs1 = xs1 - ys2
  }

  q' を qs に追加する
  ys1 の最下位から 0 を取り除く
  s = s - 1
}

商は qs, 剰余は xs1 / d となる。

ポイントは仮の商 q' を求める処理です。ys1 の最上位の桁 ym が条件 (A) base / 2 <= ym < base を満たしている場合、(2) であれば q' は 0 か 1 になります。(3) であれば xs1 の上位 2 桁と ys1 の上位 1 桁 (ym) から仮の商を求めます。このとき、真の商を q とすると、条件 (A) を満たしているならば次の式が成り立ちます。

q <= q' <= q + 2

したがって、q の値は q', q' - 1, q' - 2 のどれかになります。ys2 = ys1 * q' を計算し、xs1 < ys2 であれば q' から 1 を、ys2 から ys1 を引き算します。これを xs1 >= ys2 になるまで繰り返しますが、最悪でも 2 回の繰り返しで済むわけです。

商 q が q' - 1 と q' - 2 になる例を示します。

xs1 = [65535, 65535, 32767]
ys1 = [65535, 32768]

q' = (32767 * base + 65535) / 32768 = 65535
ys2 = [65535, 32768] * 65535 = [1, 32766, 32768] > xs1

q' = q' - 1 = 65534
ys2 = ys2 - ys1 = [2, 65533, 32767] < xs1

q' = 65534, xs1 = xs1 - ys2 = [65533, 2]

-----------------------------------------------------
xs1 = [65535, 0, 32767]
ys1 = [65535, 32768]

q' = (32767 * base + 0) / 32768 = 65534
ys2 = [65535, 32768] * 65534 = [2, 65533, 32767] > xs1

q' = q' - 1
ys2 = ys2 - ys1 = [3, 32764, 32767] > xs1
q' = q' - 1
ys2 = ys2 - ys1 = [4, 65531, 32766] < xs1

q' = 65532, xs1 = xs1 - ys2 = [65531, 5]

なお、(3) を満たしているとき、より高い精度で仮の商 q' を求める方法があります。有名なクヌース先生のアルゴリズムDはこの方法を使っています。除算のアルゴリズムについては、参考文献 [2] がわかりやすくまとまっていると思います。また、乗算の処理が高速な場合、ニュートン法で ys の逆数 1 / ys を求め、xs * (1 / ys) を計算することで除算を高速に実行することができます。

擬似コードをそのままプログラムすると次のようになります。

リスト : 多倍長整数の除算

halfBase = 0x8000

-- シフトするビット数を求める
getShiftBit :: Word32 -> Int
getShiftBit n = iter n 0
  where iter n c
          | n >= halfBase = c
          | otherwise = iter (n * 2) (c + 1)

-- 仮の商を求める
getQuot :: Bignum -> Bignum -> Word32
getQuot [] _ = 0
getQuot [x] [y] = x `div` y
getQuot (x1:x2:xs) [y] = (x2 * base + x1) `div` y
getQuot (_:xs) (_:ys) = getQuot xs ys


bignumDiv :: Bignum -> Bignum -> (Bignum, Bignum)
bignumDiv xs [y] = bignumDivInt xs y
bignumDiv xs ys
  | bignumLT xs ys = ([0], xs)
  | otherwise = iter s xs' ys' []
      where d = getShiftBit $ last ys
            xs' = bignumShiftLeft xs d
            s = length xs' - length ys
            ys' = bignumShiftLeft ys (baseBit * (fromIntegral s) + (fromIntegral d))
            iter s xs1 ys1 q =
              if s < 0
              then (q, bignumShiftRight xs1 d)
              else iter (s - 1)
                        xs1'
                        (tail ys1)
                        (if quot == 0 && null q then q else quot:q)
              where (quot, xs1') = iter2 xs1 ys1
                    iter2 xs1 ys1 =
                      let quot = min (getQuot xs1 ys1) (base - 1)
                          ys2 = bignumMulInt ys1 quot
                      in if quot == 0
                         then (quot, xs1) 
                         else iter3 xs1 ys1 ys2 quot
                    iter3 xs1 ys1 ys2 quot =
                      if bignumLT xs1 ys2
                      then iter3 xs1 ys1 (bignumSub ys2 ys1) (quot - 1)
                      else (quot, bignumSub xs1 ys2)

関数 getShiftBit は ys の最上位の値が base / 2 以上になるよう、左シフトするビット数を求めます。関数 getQuot は仮の商を求めます。xs が空リストならば、xs の桁は ys よりも少ないので 0 を返します。ys が末尾の要素で、かつ xs も末尾の要素であれば、同じ桁数なので x / y を返します。そうでなければ、xs の上位 2 桁を求め、それを y で割り算します。関数 bignumDiv は説明をそのままプログラムしただけなので、とくに難しいところはないと思います。

-- 参考文献 --------
[1] Fussy's HOMEPAGE, 多倍長整数の演算
[2] 野呂春文, 大きな整数の除算アルゴリズム (PDF)

●解答97

リスト : 多倍長整数を文字列に変換する

charTable :: String
charTable = "0123456789ABCDEF"

bignumToString :: Bignum -> Word32 -> String
bignumToString xs r = iter xs []
  where iter [0]    a = a
        iter xs a = iter ys ((charTable !! (fromIntegral m)) : a)
          where (ys, [m]) = bignumDivInt xs r

bignumToString は簡単です。bignumDivInt で xs を基数 r で割り算し、charTable から m 番目の要素を求め、それを累積変数 a のリストに追加します。この処理を xs が [0] になるまで繰り返し、最後に a を返します。

●解答98

リスト : 文字列を多倍長整数に変換する

stringToBignum :: String -> Word32 -> Bignum
stringToBignum s r = iter s [0]
  where iter [] a = a
        iter (x:xs) a =
          case findIndex (== toUpper x) charTable of
            Nothing -> error "stringToBignum: illegal char"
            Just n  -> iter xs (bignumAddInt (bignumMulInt a r) (fromIntegral n))

stringToBignum も簡単です。局所関数 iter で 1 文字ずつ順番に取り出し、関数 findIndex で文字を数値 n に変換します。このとき、英小文字をモジュール Data.Char の関数 toUpper で英大文字に変換しています。文字が見つからない場合はエラーを送出します。あとは、bignumMulInt で累積変数 a と基数 r を掛け算し、それに bignumAddInt で n を加算していくだけです。最後に a を返します。

●解答99

数 a の平方根 √a の値を求める場合、方程式 x2 - a = 0 を Newton (ニュートン) 法で解くことが多いと思います。方程式を f(x), その導関数を f'(x) とすると、ニュートン法は次の漸化式の値が収束するまで繰り返す方法です。

xn+1 = xn - f(xn) / f'(xn)

平方根を求める場合、導関数は f'(x) = 2x になるので、漸化式は次のようになります。

xn+1 = (xn + a / xn) / 2

参考文献 1 によると、√a より大きめの初期値から出発し、置き換え x <- (x + a / x) / 2 を減少が止まるまで繰り返すことで √a の正確な値を求めることができるそうです。

これをプログラムすると次のようになります。

リスト : 平方根の整数部を求める

bignumSqrt :: Bignum -> Bignum
bignumSqrt [0] = [0]
bignumSqrt xs = iter xs
  where iter p = let (q, _) = bignumDiv xs p
                     q' = bignumShiftRight16 (bignumAdd p q) 1
                 in if bignumGE q' p then p else iter q'

漸化式を計算して変数 q' にセットし、q' がひとつ前の値 p 以上になったら p を返すだけです。

ところで、Newton 法は初期値の設定によって性能が大きく変わります。引数 xs をそのまま初期値として使う場合、xs が大きくなると実行速度はかなり遅くなります。そこで、xs を半分に分けて、上位の桁を初期値として使うことにします。ただし、そのままでは √xs よりも小さくなる場合があるので、16 ビット左シフトすることにします。

プログラムは次のようになります。

リスト : 平方根の整数部を求める (改良版)

bignumSqrt' :: Bignum -> Bignum
bignumSqrt' [0] = [0]
bignumSqrt' xs = iter (init xs)
  where init xs = let k = length xs `div` 2
                  in if k == 0 then xs else 0 : drop k xs
        iter p = let (q, _) = bignumDiv xs p
                     q' = bignumShiftRight16 (bignumAdd p q) 1
                 in if bignumGE q' p then p else iter q'

init で初期値を求めます。xs を半分の桁を k とし、k が 0 ならば xs をそのまま返します。そうでなければ、drop で xs から k 個の要素を削除し、その先頭に 0 を追加します。

簡単な実行例を示します。

*Main> bignumToInteger $ bignumSqrt $ toBignum $ 2 * 10^100
141421356237309504880168872420969807856967187537694
(0.28 secs, 26877204 bytes)
*Main> bignumToInteger $ bignumSqrt' $ toBignum $ 2 * 10^100
141421356237309504880168872420969807856967187537694
(0.09 secs, 7789172 bytes)
*Main> bignumToInteger $ bignumSqrt $ toBignum $ 2 * 10^200
14142135623730950488016887242096980785696718753769480731766797379907324784621070
388503875343276415727
(1.59 secs, 164479284 bytes)
*Main> bignumToInteger $ bignumSqrt' $ toBignum $ 2 * 10^200
14142135623730950488016887242096980785696718753769480731766797379907324784621070
388503875343276415727
(0.19 secs, 16584340 bytes)

実行環境 : Windows 7, Core i7-2670QM 2.20GHz, GHCi, version 7.4.1

bignumSqrt' のほうが高速ですね。改良の効果は十分に出ていると思います。

-- 参考文献 --------
1. 奥村晴彦,『C言語による最新アルゴリズム事典』, 技術評論社, 1991

Copyright (C) 2013 Makoto Hiroi
All rights reserved.

[ PrevPage | Haskell | NextPage ]