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  }