juliaで可変ワード長の演算を試す

juliaが結構便利なので、勉強がてら有限ワード長のモジュールを作っています。 その過程をメモ代わりにそのまま吐き出してみようと思います。

  • 概要を考える

ワード長は可変にしたいので、値とワード長の二つのフィールドが必要になりそうです。値の方は、簡単のため UInt で済ませます。 演算をするのであれば、コンストラクタと加減算、あとは、連結はほしいところです。

というざっくりしたイメージでスタートしました。

  • コンストラクタ

こちらは、特に悩む必要もなくtypeを使うだけで終わります。 任意ワード長の RegVarのほかに、8bit固定の Reg8 も用意しておこうかと思い、これらをまとめる抽象型 AbstractReg も作りました。

abstract AbstractReg
type RegVar <: AbstractReg
  val :: UInt
  wl :: Int
end
type Reg8 <: AbstractReg
  val :: Uint
end
  • 加算

Baseの+関数をimportして、ワード長を考慮しながら計算します。

import Base: +
export +
+(a::RegVar, b::RegVar) = addreg(a, b)
function addreg%(a::RegVar, b::RegVar)
  n = max(a.wl, b.wl)
  ub = 2^(n-1)-1
  lb = -2^(n-1)
  y = a.val + b.val
  if (y < lb)
    y = y + 2^n
  else (y > ub)
    y = y - 2^n
  end
  RegVar(y, n)
end

RegVar としてはこれでいいのですが、Reg8に適用しようとしてもReg8はwlをメンバに持っていないのでうまくいきません。かといって、Reg8にwlを追加するのも嫌だな……。 と考えたところで、wl を総称関数にすればよいことに気づきました。

wl(::Reg8) = 8
wl(x::RegVar) = x.wl

これをつかって先ほどのaddregを書き換えると、こうなります。

import Base: +
export +
+(a::AbstractReg, b::AbstractReg) = addreg(a, b)
function addreg(a::AbstractReg, b::AbstractReg)
  n = max(wl(a), wl(b))
  ub = 2^(n-1)-1
  lb = -2^(n-1)
  y = a.val + b.val
  if (y < lb)
    y = y + 2^n
  else (y > ub)
    y = y - 2^n
  end
  RegVar(y, n)
end

a.wl などとしていたところを wl(a) にしてあげただけですが、これで Reg8 にも RegVar にも対応できるようになりました。

Juliaの場合、型のフィールドに対して自動的にアクセサが生成されるのですが、総称関数を定義するとこういうときに便利なのですね。納得。

  • 減算

減算は、単項演算子のマイナスを定義して、先ほどの加算と組み合わせることにします。

import Base: -
export -
-(x::AbstractReg) = RegVar((1 << wl(x)) - x.val, wl(x))
-(a::AbstractReg, b::AbstractReg) = a + (-b)

ワード長が64の時にどうするんだという話はありますが、とくに問題ないようです。

julia> convert(UInt, 1) << 64 - 1
0xffffffffffffffff
  • 連接

このあたりから自信がなくなってきます。 演算子は ++ にすることにして、二項演算子としてはすぐに書けました。ただ、これを連続して使おうとした場合にどうするかで手が止まります。 よくわからないので、julia本体から似たようなコードを探してみることに。sum なら可変長引数をとるかとおもって確認してみたところ、tuple.jlに記載がありそうです。

julia> methods(sum)
# 13 methods for generic function "sum":
sum(x::Tuple{Any,Vararg{Any,N<:Any}}) at tuple.jl:205
...

そこを確認したところ、ドット3つでタプルを展開してくれるような感じです。いわれてみれば、MATLABにそんなのがあったような気もします。

export ++
++(args...) = assoc(args)
assoc(x::Tuple{Any, Vararg{Any}}) = assoc(x...)
assoc(a::RegVar, args...) = assoc(a, assoc(args))
...

こんな感じで展開できました。

  • 補足:表示、等号

書き始めた当初に、完全に忘れていたのが表示と等号。これらがないと REPL やテストで困ることに、後で気づきました。 表示については、juliaの本体を検索すると、showという関数を定義すればよさそうなことがわかりました。

> ag "print\(" *.jl
...
version.jl
48:function print(io::IO, v::VersionNumber)
49:    v == typemax(VersionNumber) && return print(io, "∞")
50:    print(io, v.major)
51:    print(io, '.')
52:    print(io, v.minor)
53:    print(io, '.')
54:    print(io, v.patch)
...

というわけで、定義します。

import Base: show
function show(io::IO, x::AbstractReg)
  mroundup(x, n) = Int(ceil(x / n)) * n;
  print(io, wl(x));
  print(io, "'h");
  octmask = 1 << 4 -1;
  for i=mroundup(wl(x), 4)-4:-4:0
    z = octmask & (x.val >> i);
    if z < 10
      print(io, z);
    else
      print(io, 'a'+ (z-10));
    end
  end
end

これだけ定義すれば、REPL や print 関数などから自動的に呼んでもらえます。

julia> Reg8(10)
8'h0a

等号はこんな感じ。

==(a::AbstractReg, b::AbstractReg) = (a.val == b.val) && (wl(a) == wl(b))

できた。