Skip to main content

lvext

Exact pure-Python codec for x87 80-bit extended-precision floats.

Install

pip install lvext

Usage

from decimal import Decimal
from fractions import Fraction

from lvext import Ext80, ExtKind, Rounding

# Every float fits exactly, and the bytes come out big-endian unless you ask.
one = Ext80.from_float(1.0)
assert one.hex() == "3fff8000000000000000"
assert one.to_bytes("little") == bytes.fromhex("0000000000000080ff3f")

# Decode to the exact value: a Fraction, or a Decimal with every digit.
value = Ext80.from_hex("4000 c90f daa2 2168 c235")
assert value.kind is ExtKind.NORMAL
assert str(value.to_decimal()).startswith("3.14159265358979323851280895940618620443")

# Narrow to a float; the result says whether anything was lost.
assert value.narrow() == (3.141592653589793, False)

# Build from exact numbers, rounding once, in any of the four x87 modes.
third = Ext80.from_fraction(Fraction(1, 3), Rounding.TOWARD_ZERO)
assert third.hex() == "3ffdaaaaaaaaaaaaaaaa"
assert Ext80.from_decimal(Decimal("0.1")).hex() == "3ffbcccccccccccccccd"

The float-in, float-out helpers from the first release are still there:

from lvext import pack_ext, unpack_ext

assert pack_ext(-2.5) == bytes.fromhex("c000a000000000000000")
assert unpack_ext(pack_ext(-2.5)) == -2.5

What it handles

  • The format. 1 sign bit, a 15-bit exponent biased by 16383, an explicit integer bit and a 63-bit fraction (Intel SDM Vol. 1, 4.2 and 8.2.2). Ext80 holds the three fields bit for bit, so all 2**80 encodings round-trip through bytes, hex and integers in either byte order. x87 memory order is little-endian.
  • Exact values. to_fraction() and to_decimal() never round; the Decimal holds every digit (thousands of them near the ends of the range) whatever the context precision.
  • Rounding. from_fraction, from_decimal and from_number round once to 64 mantissa bits, and narrow/to_float round once to a double, with Rounding.NEAREST_EVEN (the default), FLOOR, CEILING or TOWARD_ZERO. Underflow is gradual; overflow gives infinity or the largest finite value as IEEE 754 prescribes for the mode.
  • Special values. Signed zero, infinities, quiet and signaling NaN. A float NaN keeps its sign and payload on the way in; on the way out a float keeps the top 51 payload bits, and a signaling NaN comes out quiet, as the FPU does. A Decimal NaN maps its payload to the low 62 bits of the mantissa (a float payload p therefore reads back as p << 11), and Decimal("sNaN") becomes sNaN1, because a signaling NaN needs a nonzero fraction.
  • Legacy encodings. ExtKind also names the encodings the 80387 and later reject: unnormal, pseudo-infinity and pseudo-NaN. They have no value, so to_fraction() raises InvalidEncodingError and to_float() returns the x87 indefinite NaN, as the FPU does. A pseudo-denormal is accepted with the value of exponent 1; canonical() rewrites it.
  • Errors. Bad input (wrong type, length, byte order, hex or range) raises LvextError, which is a ValueError. NotFiniteError is raised when an infinity or NaN is asked for a Fraction.
  • Limits. EXT80_MAX, EXT80_MIN_NORMAL, EXT80_MIN_SUBNORMAL, EXT80_EPSILON and EXT80_INDEFINITE are Ext80 constants.

Command line

$ lvext encode 1
3fff8000000000000000
$ lvext encode 1/3 --rounding toward-zero
3ffdaaaaaaaaaaaaaaaa
$ lvext encode -inf
ffff8000000000000000
$ lvext decode 3ffd8000000000000000
kind: normal
sign: +
exponent: 0x3ffd
mantissa: 0x8000000000000000
exact: 0.25
float: 0.25
float_hex: 3fd0000000000000
float_exact: yes

encode takes a decimal, a fraction such as 1/3, inf, nan, snan or -0, with --byteorder, --rounding and --width 16 (zero-padded container). decode takes 20 hex digits, or 32 for a 16-byte container, and --json prints one JSON object. Errors go to stderr with exit status 1. lvext --help lists examples.

How it is verified

Each golden vector in the contract tests is an exact rational written with fractions.Fraction. On Windows x86-64 the test suite also runs the FPU's own load and store instructions (FLD, FSTP, FILD, FDIVP) through a small test-only ctypes helper and compares lvext with the hardware bit for bit, under all four rounding modes, over the edge cases and thousands of random encodings. That check found and now guards three decode bugs in 0.1.0 (see the changelog).

LabVIEW

LabVIEW's EXT type is extended precision, but its flattened form is 16 bytes and NI does not publish that layout in detail. lvext implements the x87 80-bit format only. It does not decode the 16-byte flattened EXT, and none of its output has been verified against LabVIEW. pack_ext128 and unpack_ext128 are a zero-padded 16-byte container (with "little", the layout of a 16-byte long double on x86-64 compilers such as GCC and Clang), not a documented LabVIEW format.

Why

The Python standard library cannot read this format: struct has no 80-bit code. numpy.longdouble exposes it only where the platform's long double is the x87 type (for example Linux with GCC); on Windows with MSVC it is a 64-bit double. lvext does the bit math in pure Python with no dependencies, so the same bytes decode the same way on every platform, and exact conversions to Fraction and Decimal come with it.

License

MIT. See LICENSE.

Metadata

Release files for lvext 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lvext 0.2.0
File Size Uploaded
lvext-0.2.0.tar.gz 28.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for lvext 0.2.0
File Interpreter ABI Platform
lvext-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.3 kB

Release files / lvext-0.2.0.tar.gz

Download URL lvext-0.2.0.tar.gz
Size 28.1 kB
Tags Source
SHA-256 checksum
How to use checksums
594591ab3c108170ef193555910aaa5be6d419c09c42685193e7f4ef3831dd47
BLAKE2b-256 checksum
How to use checksums
33bf834e7832c55b38b5e8b1cf1037b055304732c2f381491faf4af8c65a59d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release files / lvext-0.2.0-py3-none-any.whl

Download URL lvext-0.2.0-py3-none-any.whl
Size 18.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a46c78f91dce8a8cd4f1fcaa7cf3e908c01c293575cc34d3bebf8eb5814ff5e7
BLAKE2b-256 checksum
How to use checksums
8095d31fb1bb792a752ca429f5203eb702f65d246dc8d90ecb1af8f83111d988
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.2

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page