Arrays and values#

ExactRef stores data as raw bits with a format, so every value it reads or returns is exact: bfloat16 and float8 values keep their encodings, and -0, infinities and NaN keep their identity. Its arrays are immutable, so an input cannot change during a calculation.

ExactRef holds data in Array (immutable) and MutableArray (editable): one flat byte buffer plus a dtype, a shape and a byte order. asarray imports data into them. A FloatValue or ComplexValue is the exact value of one element. Data in and out walks through them with examples.

At a glance#

To

Use

import data

er.asarray(obj, dtype=None, rounding=None, overflow=None)

read values

a.tolist(), a.scalars(), a.scalar(index)

read exact values

a.value(index), a.values()

read raw bits

a.encoding(index), a.encodings(), a.buffer

read the layout

a.dtype, a.shape, a.size, a.ndim, a.nbytes

change the layout

a.reshape(shape)

build an array

er.Array.zeros(dtype, shape), er.Array.from_values(dtype, shape, values), er.Array.from_encodings(dtype, shape, encodings)

edit values

a.mutable_copy(), then m.set_value(index, value) and m.freeze()

test a bool array

a.all(), a.any()

compare bit for bit

a.identical(b)

asarray#

asarray(obj, dtype: Format | str | None = None, *, rounding: Rounding | None = None, overflow: str | None = None) -> Array#

Import data as an immutable Array.

Accepted sources:

  • Array (returned as is) and MutableArray (frozen copy).

  • Python scalars and nested lists or tuples. Without dtype, bools become bool, ints int64, floats float64 and complex numbers complex128; a value the inferred format cannot hold exactly raises. Fraction, FloatValue and ComplexValue need an explicit dtype.

  • Objects with the buffer protocol whose element format is known: NumPy arrays, array.array, memoryview, and bytes (as uint8).

  • CPU tensors with __dlpack__ (PyTorch, JAX, NumPy), including bfloat16 and float8. A PyTorch tensor's lazy conjugate and negative views are applied first, and one that requires grad is detached.

Data that carries its own format is imported exactly. When dtype names a different format, values are converted with rounding and overflow. Following PyTorch, converting array data to an integer dtype wraps on overflow by default (like tensor.to), while Python values that do not fit raise (like torch.tensor); other defaults are each format's own: in a format without infinity, a value past the largest rounds within the format's values (FloatFormat.encode). Converting to bool reads every nonzero value, NaN included, as True, as PyTorch does. To reinterpret raw bytes instead, use Array(dtype, shape, buffer).

Array#

class Array(dtype: Format | str, shape, buffer, *, byteorder: str = 'little')#

An immutable array, suitable as a reference input.

Its storage is a bytes object that no caller can modify, so values cannot change during a calculation.

dtype#

The element format, a Format.

shape#

The shape, a tuple of ints.

ndim#

The number of dimensions.

nbytes#

The size of the buffer in bytes.

byteorder#

"little" or "big".

property buffer: bytes#

The underlying bytes, for bulk routines.

reshape(shape) -> Array#

Return an array with the same storage and element count but a new shape.

mutable_copy() -> MutableArray#

Return a mutable array with independent storage.

all() -> bool#

Whether every element is true: nonzero, as in PyTorch (NaN is true, a zero of either sign false). True for an empty array.

any() -> bool#

Whether some element is true (nonzero, as all reads it). False for an empty array.

byte_offset(index=()) -> int#

Return the byte at which element index starts.

encoding(index=())#

Return the raw encoding of one element: an unsigned int, or a pair for complex.

encodings() -> list#

Return every raw encoding in row-major order.

classmethod from_encodings(dtype: Format | str, shape, encodings, *, byteorder: str = 'little')#

Build an array from raw encodings in row-major order.

classmethod from_values(dtype: Format | str, shape, values, *, byteorder: str = 'little', **options)#

Build an array by encoding values in row-major order.

options (such as rounding) are passed to dtype.encode.

identical(other: _ArrayBase) -> bool#

Whether other has the same dtype, shape and encodings (byte order ignored).

scalar(index=())#

Return one element as an ordinary Python float, int or bool.

scalars() -> list#

Return every element as a Python scalar in row-major order.

property size: int#

The number of elements.

tolist()#

Return the elements as nested lists of Python scalars (a bare scalar for shape ()).

value(index=())#

Return the exact value of one element (a FloatValue, int or bool).

values() -> list#

Return every exact value in row-major order.

classmethod zeros(dtype: Format | str, shape, *, byteorder: str = 'little')#

Return an array whose every byte is zero (+0, 0 or False).

MutableArray#

class MutableArray(dtype: Format | str, shape, buffer, *, byteorder: str = 'little')#

An array whose elements can be set, for data being built or edited.

Writes go through the codec (set_value) or checked bulk methods. Call freeze() to obtain an immutable Array.

property buffer: memoryview#

A read-only view of the current bytes, for bulk routines.

set_encoding(index, bits) -> None#

Store a raw encoding (an int, or a pair for complex) at one element.

set_value(index, value, **options) -> None#

Encode a value and store it at one element.

options (such as rounding) are passed to dtype.encode.

set_encodings(encodings) -> None#

Replace every element with raw encodings in row-major order.

set_values(values, **options) -> None#

Replace every element by encoding values in row-major order.

set_bytes(data) -> None#

Replace the whole buffer with bytes of exactly the same length.

freeze() -> Array#

Return an immutable copy. This array remains usable.

copy() -> MutableArray#

Return a mutable array with independent storage.

MutableArray also has every reading method of Array.

FloatValue#

class FloatValue(kind: str, negative: bool, significand: int = 0, exponent: int = 0)#

The exact value of one binary floating-point encoding.

A finite value equals (-1)**negative * significand * 2**exponent. The pair is normalized (significand is odd, or zero with exponent == 0), so equal values have equal fields. Unlike Fraction, a FloatValue keeps signed zeros, infinities and NaN.

Equality is structural, not IEEE comparison: a NaN equals another NaN of the same sign, and -0 differs from +0. NaN payloads are not represented; read raw encodings when a payload must be preserved.

classmethod from_float(x: float) -> FloatValue#

Return the exact value of a Python float, including its sign.

to_fraction() -> Fraction#

Return the exact rational value. The sign of zero is not preserved.

Code
import exactref as er

# The exact value of the Python float 0.75, which is 3 * 2**-2
v = er.FloatValue.from_float(0.75)
print("value:", repr(v))

# The same value as an exact fraction
print("fraction:", repr(v.to_fraction()))
# Whether it is finite, and whether it is zero
print("finite:", v.is_finite)
print("zero:", v.is_zero)

# Negative infinity, a value a Fraction cannot hold
print("infinite:", er.FloatValue.infinity(negative=True).is_infinite)
Output
value: FloatValue(kind='finite', negative=False, significand=3, exponent=-2)
fraction: Fraction(3, 4)
finite: True
zero: False
infinite: True

ComplexValue#

class ComplexValue(real: FloatValue, imag: FloatValue)#

The exact value of one complex encoding: a pair of FloatValue parts.

classmethod from_complex(z: complex) -> ComplexValue#

Return the exact value of a Python complex, including signed zeros.

Code
# The exact value of the Python complex 1.5 - 2j: a FloatValue for each part
c = er.ComplexValue.from_complex(1.5 - 2j)
# Each part as an exact fraction
print("real:", repr(c.real.to_fraction()))
print("imag:", repr(c.imag.to_fraction()))
Output
real: Fraction(3, 2)
imag: Fraction(-2, 1)