preserves/implementations/python/single-precision.md

2.4 KiB

The code below deals with expansion of single-precision IEEE 754 floating point values to double-precision (and vice-versa) without losing detail of NaN bit-patterns.

def to_bytes(self):
    """Converts this 32-bit single-precision floating point value to its binary32 format,
    taking care to preserve the quiet/signalling bit-pattern of NaN values, unlike its
    `struct.pack('>f', ...)` equivalent.

    ```python
    >>> Float.from_bytes(b'\\x7f\\x80\\x00{')
    Float(nan)
    >>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()
    b'\\x7f\\x80\\x00{'

    >>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]
    nan
    >>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()
    b'\\x7f\\xc0\\x00{'
    >>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])
    b'\\x7f\\xc0\\x00{'

    ```

    (Note well the difference between `7f80007b` and `7fc0007b`!)

    """

    if math.isnan(self.value) or math.isinf(self.value):
        dbs = struct.pack('>d', self.value)
        vd = struct.unpack('>Q', dbs)[0]
        sign = vd >> 63
        payload = (vd >> 29) & 0x007fffff
        vf = (sign << 31) | 0x7f800000 | payload
        return struct.pack('>I', vf)
    else:
        return struct.pack('>f', self.value)

@staticmethod
def from_bytes(bs):
    """Converts a 4-byte-long byte string to a 32-bit single-precision floating point value
    wrapped in a [Float][preserves.values.Float] instance. Takes care to preserve the
    quiet/signalling bit-pattern of NaN values, unlike its `struct.unpack('>f', ...)`
    equivalent.

    ```python
    >>> Float.from_bytes(b'\\x7f\\x80\\x00{')
    Float(nan)
    >>> Float.from_bytes(b'\\x7f\\x80\\x00{').to_bytes()
    b'\\x7f\\x80\\x00{'

    >>> struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]
    nan
    >>> Float(struct.unpack('>f', b'\\x7f\\x80\\x00{')[0]).to_bytes()
    b'\\x7f\\xc0\\x00{'
    >>> struct.pack('>f', struct.unpack('>f', b'\\x7f\\x80\\x00{')[0])
    b'\\x7f\\xc0\\x00{'

    ```

    (Note well the difference between `7f80007b` and `7fc0007b`!)

    """
    vf = struct.unpack('>I', bs)[0]
    if (vf & 0x7f800000) == 0x7f800000:
        # NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
        sign = vf >> 31
        payload = vf & 0x007fffff
        dbs = struct.pack('>Q', (sign << 63) | 0x7ff0000000000000 | (payload << 29))
        return Float(struct.unpack('>d', dbs)[0])
    else:
        return Float(struct.unpack('>f', bs)[0])