TestBench.tools

IEEE 754 Float Converter

Float ↔ hex/registers with Modbus word orders ABCD/CDAB/BADC/DCBA.

stored float32: 3.1415927410125732 (nearest representable)

ABCD — big-endian (high word first)
0x4049 0x0FDB
CDAB — word-swapped
0x0FDB 0x4049
BADC — byte-swapped within words
0x4940 0xDB0F
DCBA — little-endian
0xDB0F 0x4940

This tool converts between an IEEE 754 single-precision float and the two 16-bit Modbus registers that carry it — in all four word orders (ABCD, CDAB, BADC, DCBA) at once. Use it to encode a setpoint for writing, or to find out which byte order your PLC or sensor uses when a readback looks like garbage.

How it works

An IEEE 754 float32 is four bytes: 1 sign bit, 8 exponent bits and 23 fraction bits. Call the bytes A B C D from most to least significant. Two consecutive Modbus registers hold those four bytes, but the mapping is a vendor convention: ABCD puts the high word first (big-endian), CDAB swaps the two words, BADC swaps the bytes inside each word, and DCBA reverses everything (little-endian).

The same four bytes therefore decode to different numbers depending on the assumed order — showing all four interpretations side by side makes the correct one obvious in practice.

Worked example

value: 3.1415927 → IEEE 754 bytes: 40 49 0F DB
ABCD: 0x4049 0x0FDB  ·  CDAB: 0x0FDB 0x4049
BADC: 0x4940 0xDB0F  ·  DCBA: 0xDB0F 0x4940

All four register pairs decode back to 3.1415927 in their own order.

Parameters

ParameterValueNotes
FormatIEEE 754 binary321 + 8 + 23 bits
Registers2 × 16-bitconsecutive holding registers
ABCDreg0 = A·B, reg1 = C·Dbig-endian, high word first
CDABreg0 = C·D, reg1 = A·Bword swap
BADCreg0 = B·A, reg1 = D·Cbyte swap within words
DCBAreg0 = D·C, reg1 = B·Alittle-endian

Python implementation

Python
import struct

# float -> two 16-bit registers in each Modbus word order
def to_regs(value: float, order: str = "ABCD") -> tuple[int, int]:
    a, b, c, d = struct.pack(">f", value)
    pairs = {"ABCD": (a, b, c, d), "CDAB": (c, d, a, b),
             "BADC": (b, a, d, c), "DCBA": (d, c, b, a)}
    p = pairs[order]
    return (p[0] << 8) | p[1], (p[2] << 8) | p[3]

assert to_regs(3.1415927, "ABCD") == (0x4049, 0x0FDB)
assert to_regs(3.1415927, "CDAB") == (0x0FDB, 0x4049)

FAQ

My float reads as a nonsense value — how do I find the right word order?

Enter the two raw registers in Registers → Float mode and look at all four interpretations at once. The one producing a plausible engineering value is your device's order. For example registers 0x4049 0x0FDB read as ABCD give 3.1415927; the same pair in another order gives a completely different number.

What do ABCD, CDAB, BADC and DCBA mean?

A is the most significant byte of the IEEE 754 value and D the least. ABCD is straight big-endian (high word first), CDAB swaps the two 16-bit words, BADC swaps bytes within each word, and DCBA is fully little-endian. Vendors disagree on which to use, which is why all four exist in the wild.

Why does 3.1415927 come back as 3.1415927410125732?

A 32-bit float has about 7 significant decimal digits. The converter stores your input in the nearest representable float32 and shows that stored value — the difference is the rounding inherent to single precision, not a bug.

Which registers hold a float in Modbus?

A float32 occupies two consecutive 16-bit holding registers. The protocol itself does not define how the four bytes map onto those registers — that is a device convention, hence the word-order selector.

Is anything uploaded?

No. All conversion happens locally in your browser.

Related tools