给定Ruby的Float值,例如,

f = 12.125

我想结束一个由3个元素组成的数组,其中包含浮点数的符号(1位),指数(11位)和小数(52位)。 (Ruby的浮点数是IEEE 754 double 64位表示形式。)

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

请注意,我要的是位,而不是它们所对应的数值。例如,获取[0, -127, 1]的浮点值的1.0并不是我想要的-我想要字符串形式或等效表示形式的实际位,例如["0", "0ff", "000 0000 0000"]

最佳答案

由于Float在内部不提供功能,因此可以通过数组pack公开位数据。

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中的操作,您需要有一个整数,因此浮点数需要更多的unpack编码才能转换为64位整数。 big endian/ieee754记录的表示形式相当琐碎。我不太确定的小端序表示形式。这有点奇怪,因为您没有11位指数和52位尾数的完整字节边界。拔掉位并交换它们以获得类似于小字节序的方式变得很愚蠢,并且不确定是否正确,因为我还没有看到对布局的任何引用。因此,64位值是小尾数法,在将它们存储在其他位置(例如尾数的16位int)之前,我不太确定这如何应用于64位值的组件。

例如,从little> big获得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

直到我的大脑是大端!您会注意到正在使用的int都是big endian。我未能以另一种方式转移。

10-05 20:33
查看更多