Module: Half

Defined in:
lib/half.rb

Overview

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

Constant Summary collapse

NAN_BYTES =
"\x7e\x00"

Class Method Summary collapse

Class Method Details

.decode(b16) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/half.rb', line 23

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) ⇒ Object



19
20
21
22
# File 'lib/half.rb', line 19

def self.decode_from_bytes(hs)
  b16, = hs.unpack("n")
  self.decode(b16)
end

.encode(fv) ⇒ Object



66
67
68
# File 'lib/half.rb', line 66

def self.encode(fv)
  self.encode_from_single(fv, [fv].pack("g"))
end

.encode_from_single(fv, ss) ⇒ Object



58
59
60
61
62
63
64
# File 'lib/half.rb', line 58

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) ⇒ Object

single-precision string



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/half.rb', line 41

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