将浮点转换为其对应的位段

时间:2014-09-10 15:47:23

标签: ruby floating-point

给定Ruby Float值,例如

f = 12.125

我想结束一个包含浮点数符号(1位),指数(11位)和分数(52位)的3元素数组。 (Ruby的浮点数是IEEE 754双精度64位表示。)

最好的方法是什么?比特级操作似乎不是Ruby的强项。

请注意,我想要的是比特,而不是它们对应的数值。例如,为[0, -127, 1]的浮点值获取1.0不是我之后的事情 - 我想要字符串形式的实际位或等效表示,如{{1} }。

2 个答案:

答案 0 :(得分:4)

位数据可以通过数组pack公开,因为Float不在内部提供函数。

str = [12.125].pack('D').bytes.reverse.map{|n| "%08b" %n }.join
=> "0100000000101000010000000000000000000000000000000000000000000000"

[ str[0], str[1..11], str[12..63] ]
=> ["0", "10000000010", "1000010000000000000000000000000000000000000000000000"]

这有点'围绕房子'从字符串表示中拉出来。我确信有一种更有效的方法从原始bytes ...

中提取数据

编辑 位级操作调整了我的兴趣所以我有一个蠢货。要在Ruby中使用这些操作,你需要有一个Integer,因此float需要更多unpack来转换为64位int。 big endian / ieee754记录的表示非常简单。小端表示我不太确定。这有点奇怪,因为你没有使用11位指数和52位尾数的完整字节边界。将这些位拉出并交换它们以获得类似于小端的东西变得非常繁琐,并且不确定它是否正确,因为我没有看到任何对布局的引用。所以64位值是小端,我不太确定64bit值的组件如何适用于其他地方,直到将它们存储在其他地方,如尾数的16位int。

作为来自little>的11位值的示例我正在做的事情是将最重要的字节3左移到前面,然后用最不重要的3位移位。

v = 0x4F2
((v & 0xFF) << 3) | ( v >> 8 ))

无论如何,这是有用的。

class Float
  Float::LITTLE_ENDIAN = [1.0].pack("E") == [1.0].pack("D")

  # Returns a sign, exponent and mantissa as integers
  def ieee745_binary64
    # Build a big end int representation so we can use bit operations
    tb = [self].pack('D').unpack('Q>').first

    # Check what we are
    if Float::LITTLE_ENDIAN
      ieee745_binary64_little_endian tb
    else
      ieee745_binary64_big_endian tb
    end
  end

  # Force a little end calc
  def ieee745_binary64_little
    ieee745_binary64_little_endian [self].pack('E').unpack('Q>').first
  end

  # Force a big end calc
  def ieee745_binary64_big
    ieee745_binary64_big_endian [self].pack('G').unpack('Q>').first
  end

  # Little
  def ieee745_binary64_little_endian big_end_int
    #puts "big #{big_end_int.to_s(2)}"
    sign     = ( big_end_int & 0x80   ) >> 7

    exp_a    = ( big_end_int & 0x7F   ) << 1   # get the last 7 bits, make it more significant
    exp_b    = ( big_end_int & 0x8000 ) >> 15  # get the 9th bit, to fill the sign gap
    exp_c    = ( big_end_int & 0x7000 ) >> 4   # get the 10-12th bit to stick on the front
    exponent = exp_a | exp_b | exp_c

    mant_a   = ( big_end_int & 0xFFFFFFFFFFFF0000 ) >> 12 # F000 was taken above
    mant_b   = ( big_end_int & 0x0000000000000F00 ) >> 8  #  F00 was left over
    mantissa = mant_a | mant_b

    [ sign, exponent, mantissa ]
  end

  # Big
  def ieee745_binary64_big_endian big_end_int
    sign     = ( big_end_int & 0x8000000000000000 ) >> 63
    exponent = ( big_end_int & 0x7FF0000000000000 ) >> 52
    mantissa = ( big_end_int & 0x000FFFFFFFFFFFFF ) >> 0

    [ sign, exponent, mantissa ]
  end
end

并测试......

def printer val, vals
  printf "%-15s   sign|%01b|\n",            val,     vals[0]
  printf "  hex e|%3x|         m|%013x|\n", vals[1], vals[2]
  printf "  bin e|%011b| m|%052b|\n\n",     vals[1], vals[2]
end

floats = [ 12.125, -12.125, 1.0/3, -1.0/3, 1.0, -1.0, 1.131313131313, -1.131313131313 ]

floats.each do |v|
  printer v, v.ieee745_binary64
  printer v, v.ieee745_binary64_big
end

TIL我的大脑是大端!你会注意到正在使用的int都是big endian。我在另一个方向转移失败了。

答案 1 :(得分:3)

使用frexp模块中的Math。来自doc

fraction, exponent = Math.frexp(1234)   #=> [0.6025390625, 11]
fraction * 2**exponent                  #=> 1234.0

符号位很容易找到。