Source code for vax

################################################################################
# vax/__init__.py
################################################################################
"""PDS Ring-Moon Systems Node, SETI Institute

Functions to convert between VAX single- and double-precision floats and IEEE floats.
Conversions to/from VAX single precision are supported. However, only conversion from VAX
doubles to IEEE doubles is supported, not the reverse.

This module continues to support Python 2 in addition to Python 3.

Conversions to/from VAX single precision is supported. However, only conversions from VAX
doubles to IEEE doubles are supported, not the reverse.
"""

__all__ = ['from_vax32', 'to_vax32_bytes', 'to_vax32', 'from_vax64']

import numpy as np
import sys

try:
    from ._version import __version__
except ImportError:
    __version__ = 'Version unspecified'

_PYTHON2 = sys.version_info.major <= 2


[docs] def from_vax32(data): """Return equivalent single-precision IEEE value for VAX representation. Given a byte string, array, or array-like (something that can be converted to a numpy array), interpet it as VAX float32 or complex64 values and return the equivalent single-precision IEEE value(s). Args: data (bytes or bytearray or memoryview or str or numpy array-like): The input data. If the input is an array, the shape of that array is preserved except for the last axis, which may be modified to account for the new itemsize. Returns: np.array or np.float32 or np.complex64: The interpreted IEEE value. If the input array is complex, the returned array will have dtype "<c8"; otherwise, it will have dtype "<f4". """ # Convert a string to bytes; also handle a Python 2 buffer if _PYTHON2: if isinstance(data, (str, buffer)): # pragma: no cover # noqa: F821 data = bytes(data) else: if isinstance(data, str): data = bytes(data, encoding='latin8') # Convert the object to a NumPy array with an even number of 2-byte elements if isinstance(data, (bytes, bytearray, memoryview)): nbytes = data.nbytes if isinstance(data, memoryview) else len(data) if nbytes % 4 != 0: raise ValueError('data size is not a multiple of 4 bytes') array = np.frombuffer(data, dtype='<f4') scalar = (nbytes == 4) # True to convert to scalar at the end shapeless = False newshape = (nbytes // 4,) # array shape after conversion dtype = '<f4' else: scalar = np.isscalar(data) # True to return a scalar array = np.asarray(data, order='C') shapeless = array.shape == () # True to convert back to shape () array = np.atleast_1d(array) # needed for the view to work below # Validate array and array-like data types; convert to LSB if isinstance(data, np.ndarray): key = array.dtype.kind + str(array.dtype.itemsize) if key not in {'f4', 'c8', 'u1', 'u2', 'u4', 'i1', 'i2', 'i4'}: raise ValueError('invalid data type for 4-byte array input: ' + str(array.dtype)) dtype = '<c8' if key == 'c8' else '<f4' else: # Conversion of array-like produces arrays with dtype "f8" or "c16" if array.dtype.kind == 'c': array = np.asarray(array, dtype='<c8') dtype = '<c8' elif array.dtype.kind in 'uif': array = np.asarray(array, dtype='<' + array.dtype.kind + '4') dtype = '<f4' else: raise ValueError('invalid data type for 4-byte array-like ' 'input: ' + str(array.dtype)) # Determine array shape after conversion if array.itemsize in (1, 2): if (array.shape[-1] * array.itemsize) % 4 != 0: raise ValueError('last axis size is not a multiple of 4 bytes') last_axis = (array.shape[-1] * array.itemsize) // 4 if last_axis == 1: newshape = array.shape[:-1] else: newshape = array.shape[:-1] + (last_axis,) else: newshape = array.shape itemsize = 8 if dtype == '<c8' else 4 # Convert... # Swap pairs of bytes within words to put everything in LSB order (where the # sign has the highest memory address). # |31 |15 |1 # Before: mmmmmm_m1_mmmmmm seeeeeeeemm_m0_m # After: seeeeeeeemm_m0_m mmmmmm_m1_mmmmmm # IEEE: seeeeeeeemm_m0_m mmmmmmm_m1_mmmmm pairs = array.view(dtype='u2') pairs = pairs.reshape(-1, 2) swapped = pairs[:, ::-1].copy() swapped = swapped.reshape(-1, itemsize//2) # The sign, exponent, and mantissa are now aligned with IEEE layout # Correct for the different biases of the exponent ieee = swapped.view(dtype=dtype) / 4. if scalar: return ieee[0, 0] # current shape is (1,1) elif shapeless: return ieee.reshape(()) else: return ieee.reshape(newshape)
[docs] def to_vax32_bytes(array): """Return equivalent VAX representation for value(s) as bytes. Convert this number, array, or array-like into a byte string containing the binary representation of the equivalent VAX float32 or complex64 value(s). Args: array (numpy array-like): The input data. Returns: bytes: The VAX representation for the value(s). """ # Make array contiguous, with C index order, containing 4-byte IEEE floats dtype = '<c8' if np.iscomplexobj(array) else '<f4' array = np.asarray(array, dtype=dtype, order='C') # Conversion involves multiplication by 4 and then a pairwise byte swap paired_view = (4. * np.atleast_1d(array)).view('u2') paired_view = paired_view.reshape(-1, 2) swapped = paired_view[:, ::-1].copy() return swapped.tobytes()
[docs] def to_vax32(array): """Return equivalent VAX representation for value(s) as numpy array. Convert this number, array, or array-like into an array of VAX float32 or complex64 values with the same shape. Args: array (numpy array-like): The input data Returns: np.array: The VAX representation of the value(s) stored in a numpy array. If the input is a scalar, the returned object is an array of shape (). If the input is complex, the returned array will be of dtype "<c8"; otherwise, it will be of dtype "<f4". Note that this object will not be usable for arithmetic operations in its returned form. """ # Make array contiguous, with C index order, containing 4-byte IEEE floats dtype = '<c8' if np.iscomplexobj(array) else '<f4' scalar = np.isscalar(array) array = np.asarray(array, dtype=dtype, order='C') # Construct array from converted buffer buffer = to_vax32_bytes(array) result = np.frombuffer(buffer, dtype=dtype).reshape(array.shape) if scalar: return result[()] else: return result
################################################################################ # 64-bit support: read-only ################################################################################
[docs] def from_vax64(data): """Return equivalent double-precision IEEE value for VAX representation. Given a byte string, array, or array-like (something that can be converted to a numpy array), interpet it as VAX float64 or complex128 values and return the equivalent double-precision IEEE value(s). Args: data (bytes or bytearray or memoryview or str or numpy array-like): The input data. If the input is an array, the shape of that array is preserved except for the last axis, which may be modified to account for the new itemsize. Returns: np.array or np.float64 or np.complex128: The interpreted IEEE value. If the input array is complex, the returned array will have dtype "<c16"; otherwise, it will have dtype "<f8". """ # Convert a string to bytes; also handle a Python 2 buffer if _PYTHON2: if isinstance(data, (str, buffer)): # pragma: no cover # noqa: F821 data = bytes(data) else: if isinstance(data, str): data = bytes(data, encoding='latin8') # Convert the object to a NumPy array if isinstance(data, (bytes, bytearray, memoryview)): nbytes = data.nbytes if isinstance(data, memoryview) else len(data) if nbytes % 8 != 0: raise ValueError('data size is not a multiple of 8 bytes') array = np.frombuffer(data) scalar = (nbytes == 8) # True to convert to scalar at the end shapeless = False newshape = (nbytes // 8,) # array shape after conversion dtype = '<f8' else: scalar = np.isscalar(data) # True to return a scalar array = np.asarray(data, order='C') shapeless = array.shape == () # True to convert back to shape () array = np.atleast_1d(array) # needed for the view to work below # Validate array and array-like data types key = array.dtype.kind + str(array.dtype.itemsize) if key not in {'f8', 'c16', 'u1', 'u2', 'u4', 'u8', 'i1', 'i2', 'i4', 'i8'}: raise ValueError('invalid data type for 8-byte array input: ' + str(array.dtype)) dtype = '<c16' if key[0] == 'c' else '<f8' # Determine array shape after conversion if array.itemsize <= 4: if (array.shape[-1] * array.itemsize) % 8 != 0: raise ValueError('last axis size is not a multiple of 8 bytes') last_axis = (array.shape[-1] * array.itemsize) // 8 if last_axis == 1: newshape = array.shape[:-1] else: newshape = array.shape[:-1] + (last_axis,) else: newshape = array.shape itemsize = 16 if dtype == '<c16' else 8 # Convert... # |31 |15 |1 # D-floating: mmmmmm_m1_mmmmmm seeeeeeeemm_m0_m # mmmmmm_m3_mmmmmm mmmmmm_m2_mmmmmm # IEEE: seeeeeeeeeee_m0_ mmmmmmm_m1_mmmmm # mmmmmm_m2_mmmmmm mmmmmmm_m3_mmmmm # Pairwise swap puts bytes in standard MSB order, with the sign in the byte # having the lowest memory address pairs = array.view(dtype='uint8') pairs = pairs.reshape(-1, 2) swapped = pairs[:, ::-1].copy() # Now we can treat each value as an 8-byte item in MSB order # IEEE has three extra bits of exponent, so we need to shift these values to # align the fields properly vals = swapped.reshape(-1, 8).view('>i8') vals = (vals + 4) >> 3 # add 4 first so rounding is not always toward zero # Sign, exponent, and mantissa are now positioned properly. However, the # exponents are wrong. # Correct the exponent's biases mask = (vals >= 0) vals[ mask] += 0x37e0000000000000 # noqa: E201 vals[~mask] -= 0x3820000000000000 vals = vals.reshape(-1, itemsize//8) ieee = vals.view(dtype) if scalar: return ieee[0, 0] # current shape is (1,1) elif shapeless: return ieee.reshape(()) else: return ieee.reshape(newshape)
################################################################################