module Half

16-bit floating point values (IEEE 754 Half Precision) are not supported by pack/#unpack in Ruby yet. This is a quick hack implementing en- and decoding them. (Since this is just a hack, the brief tests are in this file.)

The encoder assumes that we already have a Single-Precision byte string (e.g., from pack(ā€œgā€)), and this is taken apart and reassembled. The decoder is free-standing (trivial).

IEEE 754 can be found at: ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4610935

Constants

NAN_BYTES

Public Class Methods

decode(b16) click to toggle source
# File lib/half.rb, line 22
def self.decode(b16)
  exp = b16 >> 10 & 0x1f
  mant = b16 & 0x3ff
  val =
    if exp == 0
      Math.ldexp(mant, -24)
    elsif exp == 31
      mant == 0 ? Float::INFINITY : Float::NAN
    else
      Math.ldexp(0x400 + mant, exp-25)
    end
  if b16[15] != 0
    -val
  else
    val
  end
end
decode_from_bytes(hs) click to toggle source
# File lib/half.rb, line 18
def self.decode_from_bytes(hs)
  b16, = hs.unpack("n")
  self.decode(b16)
end
encode(fv) click to toggle source
# File lib/half.rb, line 65
def self.encode(fv)
  self.encode_from_single(fv, [fv].pack("g"))
end
encode_from_single(fv, ss) click to toggle source
# File lib/half.rb, line 57
def self.encode_from_single(fv, ss)
  if e = self.encode_from_single_bytes(ss)
    # p e.to_s(16)
    hs = [e].pack("n")
    hs if self.decode_from_bytes(hs) == fv
  end
end
encode_from_single_bytes(ss) click to toggle source
# File lib/half.rb, line 40
def self.encode_from_single_bytes(ss)        # single-precision string
  b32, = ss.unpack("N")
  s16 = b32 >> 16 & 0x8000
  mant = b32 & 0x7fffff
  exp = b32 >> 23 & 0xff
  # puts "#{fv} #{s16} #{mant.to_s(16)} #{exp}"
  if exp == 0
    s16 if mant == 0            # 0.0, -0.0
  elsif exp >= 103 && exp < 113 # denorm, exp16 = 0
    s16 + ((mant + 0x800000) >> (126 - exp))
  elsif exp >= 113 && exp <= 142 # normalized
    s16 + ((exp - 112) << 10) + (mant >> 13)
  elsif exp == 255              # Inf (handle NaN elsewhere!)
    s16 + 0x7c00 if mant == 0   # +Inf/-Inf
  end
end