[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[dennou-ruby:000203] [REQ] Integer#{hex,dec,oct,bin}, String#bin



ごとけんです

# ruby-list にしようかと一瞬思ったけど ruby-dev にします。

何度か出た話題ですが、やはり、Integer が自分をフォーマットす
るメソッドはないとメソッドチェーンを続ける際などしばしば不便
です。当時 [ruby-dev:4853] は、新井さんがかなり頑張ったので
すがまつもとさんをおとすにはいたりませんでした。ぼくもあんま
り元気ないのでなかなか議論を深める自信はありませんがとりあえ
ず提案してみます。

まず当時何がほしいかってのが問題でしたが、要するにほしいのは
いろんなものが追加できる高級な format ではなく単にn進数表記
をその場でほしいのだということをここでは主張したいと思います。
ただし n = 2,8,10,16 でよいでしょう。

それから、名前ですが、ここは素直に bin, oct, dec, hex を提案
します。to_なんとかでも悪くはないですが、考え出すとキリがあ
りません。Integer#hex の意味は自明でしょう(強引にいく作戦)。

そして、ここが本命ですが当時触れられなかった問題として区切り
というのがあります。たとえば12億6845万2035をRubyのリテラルで
は 12_6845_2035 と書けますが、逆にIntegerをこのような表記に
するのはやや厄介です。しかし、これは format の拡張で対処する
問題とも思えません(書式の拡張が難儀なので)。

そこで、つぎのようなのを考えました(というか使ってます)。

 123891790200.hex              #=> "1cd886b178b"
 123891790200.hex("22")        #=> "            1cd886b178"
 123891790200.hex("022")       #=> "0000000000001cd886b178"
 123891790200.hex("22",4)      #=> "00_0000_0000_001c_d886_b178"
 123891790200.hex("+22",4)     #=> "+0_0000_0000_001c_d886_b178"
 (-123891790200).hex("+22",4)  #=> "-0_0000_0000_001c_d886_b178"

つまり、第1引数で符号、全体の幅とゼロパディングを指定し、第2
引数で区切り幅を指定するわけです。これがあると助かることはか
なり多いし、名前も機能も最小限だと思います。また、整数が自分
のn進数表現やその区切り方を受け持つというのは自然なことです。

てなかんじの、共通のインターフェイスをもつ Integer のメソッ
ドとして2,8,10,16進数表現 bin, oct, dec, hex を要求します。

それから、16進数、10進数、8進数の文字列から整数に変換するメ
ソッドはあるのに2進数文字列からの変換メソッドを得る方法がな
いのも個人的に不便なので、String#bin もリクエストします。

対称性のために String#to_i の別名として String#dec もあると
良いと思いますが、これは特に要求しません。

-- gotoken

# いちお普段使ってるのをつけときます。

# numrepres.rb

class Integer
  def bin(fmt = "", width = 0, sep = ?_)
    represent(:bin, fmt, sep, width)
  end

  def oct(fmt = "", width = 0, sep = ?_)
    represent(:oct, fmt, sep, width)
  end

  def dec(fmt = "", width = 0, sep = ?_)
    represent(:dec, fmt, sep, width)
  end

  def hex(fmt = "", width = 0, sep = ?_)
    represent(:hex, fmt, sep, width)
  end

  private

  def represent(base, fmt, sep, width)
    case base
    when :bin, :oct, :dec
      fmt =~ /\A\+?(?:[0]?[1-9][0-9]*)?\Z/ or
	raise ArgumentError, "invalid format `#{fmt}'"
    when :hex
      fmt =~ /\A\+?(?:[0]?[1-9][0-9]*)?[xX]?\Z/ or
	raise ArgumentError, "invalid format `#{fmt}'"
    end
    case base
    when :bin
      buf = "%#{fmt}b" % self
    when :oct
      buf = "%#{fmt}o" % self
    when :dec
      buf = "%#{fmt}d" % self
    when :hex
      fmt += "x" unless /[xX]\Z/ =~ fmt
      buf = "%#{fmt}" % self
    end
    if width > 0
      buf[0].chr +
	buf[1..-1].reverse!.gsub(/#{"."*width}/,"\\&#{sep.chr}").reverse!
    else
      buf
    end
  end
end

class String
  def bin
    eval("0b" + (scan(/\A[01][01_]*/)[0]))
  end

  alias dec to_i
end