github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/quic/gquic-go/internal/utils/float16.go (about) 1 package utils 2 3 import ( 4 "bytes" 5 "io" 6 "math" 7 ) 8 9 // We define an unsigned 16-bit floating point value, inspired by IEEE floats 10 // (http://en.wikipedia.org/wiki/Half_precision_floating-point_format), 11 // with 5-bit exponent (bias 1), 11-bit mantissa (effective 12 with hidden 12 // bit) and denormals, but without signs, transfinites or fractions. Wire format 13 // 16 bits (little-endian byte order) are split into exponent (high 5) and 14 // mantissa (low 11) and decoded as: 15 // uint64_t value; 16 // if (exponent == 0) value = mantissa; 17 // else value = (mantissa | 1 << 11) << (exponent - 1) 18 const uFloat16ExponentBits = 5 19 const uFloat16MaxExponent = (1 << uFloat16ExponentBits) - 2 // 30 20 const uFloat16MantissaBits = 16 - uFloat16ExponentBits // 11 21 const uFloat16MantissaEffectiveBits = uFloat16MantissaBits + 1 // 12 22 const uFloat16MaxValue = ((uint64(1) << uFloat16MantissaEffectiveBits) - 1) << uFloat16MaxExponent // 0x3FFC0000000 23 24 // readUfloat16 reads a float in the QUIC-float16 format and returns its uint64 representation 25 func readUfloat16(b io.ByteReader, byteOrder ByteOrder) (uint64, error) { 26 val, err := byteOrder.ReadUint16(b) 27 if err != nil { 28 return 0, err 29 } 30 31 res := uint64(val) 32 33 if res < (1 << uFloat16MantissaEffectiveBits) { 34 // Fast path: either the value is denormalized (no hidden bit), or 35 // normalized (hidden bit set, exponent offset by one) with exponent zero. 36 // Zero exponent offset by one sets the bit exactly where the hidden bit is. 37 // So in both cases the value encodes itself. 38 return res, nil 39 } 40 41 exponent := val >> uFloat16MantissaBits // No sign extend on uint! 42 // After the fast pass, the exponent is at least one (offset by one). 43 // Un-offset the exponent. 44 exponent-- 45 // Here we need to clear the exponent and set the hidden bit. We have already 46 // decremented the exponent, so when we subtract it, it leaves behind the 47 // hidden bit. 48 res -= uint64(exponent) << uFloat16MantissaBits 49 res <<= exponent 50 return res, nil 51 } 52 53 // writeUfloat16 writes a float in the QUIC-float16 format from its uint64 representation 54 func writeUfloat16(b *bytes.Buffer, byteOrder ByteOrder, value uint64) { 55 var result uint16 56 if value < (uint64(1) << uFloat16MantissaEffectiveBits) { 57 // Fast path: either the value is denormalized, or has exponent zero. 58 // Both cases are represented by the value itself. 59 result = uint16(value) 60 } else if value >= uFloat16MaxValue { 61 // Value is out of range; clamp it to the maximum representable. 62 result = math.MaxUint16 63 } else { 64 // The highest bit is between position 13 and 42 (zero-based), which 65 // corresponds to exponent 1-30. In the output, mantissa is from 0 to 10, 66 // hidden bit is 11 and exponent is 11 to 15. Shift the highest bit to 11 67 // and count the shifts. 68 exponent := uint16(0) 69 for offset := uint16(16); offset > 0; offset /= 2 { 70 // Right-shift the value until the highest bit is in position 11. 71 // For offset of 16, 8, 4, 2 and 1 (binary search over 1-30), 72 // shift if the bit is at or above 11 + offset. 73 if value >= (uint64(1) << (uFloat16MantissaBits + offset)) { 74 exponent += offset 75 value >>= offset 76 } 77 } 78 79 // Hidden bit (position 11) is set. We should remove it and increment the 80 // exponent. Equivalently, we just add it to the exponent. 81 // This hides the bit. 82 result = (uint16(value) + (exponent << uFloat16MantissaBits)) 83 } 84 85 byteOrder.WriteUint16(b, result) 86 }