Make memcmp sort your numbers correctly
Measure your machineNeeds Python
Problem
Key-value stores and sort-based engines compare keys as raw bytes (memcmp: byte by byte, unsigned). For that to sort numbers correctly, the encoding must be order-preserving. Big-endian two's complement is not: negative numbers start with a 1 bit and sort after the positives. Flipping the sign bit fixes integers. Floats need more: flip the sign bit of positive numbers and invert every bit of negative ones, so that more-negative values sort first. Composite keys need a separator that cannot appear inside a field, or ("ab", "c") and ("a", "bc") become the same bytes.
Prove the sign-bit flip and the float rule by testing them. (Python compares bytes the same way as memcmp: byte by byte, unsigned.) Then try the composite key ("ab","c") versus ("a","bc") with and without the 0x00 0xFF escape and 0x00 0x01 terminator, and check that ordering and uniqueness both hold.
The question. Which encodings make byte order equal numeric order, and does escaping keep composite keys unique and ordered?
Predict
Naive integers: False. Fixed integers: True. Floats with the rule: True.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
make-memcmp-sort-your-numbers-correctly.py # /// script # requires-python = ">=3.10" # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Make memcmp sort your numbers correctly. Encode signed integers and floats so that comparing the bytes (unsigned, byte by byte, like memcmp) gives numeric order: flip the sign bit of an integer; for a float, invert all bits if negative, else set the sign bit. Then composite string keys, with and without an escape and a terminator. """ import argparse, json, random, struct p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--values", type=int, default=10_000, help="Values per test.") p.add_argument("--seed", type=int, default=0, help="Random seed.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() random.seed(args.seed) MASK = (1 << 64) - 1 def enc_i64(n): return struct.pack(">Q", (n & MASK) ^ (1 << 63)) def enc_f64(x): b = struct.unpack(">Q", struct.pack(">d", x))[0] b = (~b & MASK) if (b >> 63) else (b | (1 << 63)) return struct.pack(">Q", b) ints = [random.randint(-10**12, 10**12) for _ in range(args.values)] floats = [random.uniform(-1e6, 1e6) for _ in range(args.values)] naive = lambda n: struct.pack(">q", n) naive_f = lambda x: struct.pack(">d", x) def concat(parts): return b"".join(s.encode() for s in parts) def escaped(parts): return b"".join(s.encode().replace(b"\x00", b"\x00\xff") + b"\x00\x01" for s in parts) keys = [("ab", "c"), ("a", "bc"), ("a", "b"), ("ab", ""), ("a", "")] rows = [ {"test": "integers, plain two's complement", "sorted_correctly": sorted(ints, key=naive) == sorted(ints)}, {"test": "integers, sign bit flipped", "sorted_correctly": sorted(ints, key=enc_i64) == sorted(ints)}, {"test": "floats, plain IEEE bytes", "sorted_correctly": sorted(floats, key=naive_f) == sorted(floats)}, {"test": "floats, sign rule", "sorted_correctly": sorted(floats, key=enc_f64) == sorted(floats)}, {"test": "composite keys, plain concatenation unique", "sorted_correctly": len({concat(k) for k in keys}) == len(keys)}, {"test": "composite keys, escaped + terminated unique", "sorted_correctly": len({escaped(k) for k in keys}) == len(keys)}, {"test": "composite keys, escaped + terminated order", "sorted_correctly": sorted(keys, key=escaped) == sorted(keys)}, ] for r in rows: r["result"] = "yes" if r.pop("sorted_correctly") else "no" print(f"{r['test']:<46} {r['result']}") print("('ab','c') and ('a','bc') concatenated:", concat(("ab", "c")), concat(("a", "bc")), "-> the same bytes") if args.json: with open(args.json, "w") as f: json.dump({"tables": {"tests": rows}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run make-memcmp-sort-your-numbers-correctly.py uv run make-memcmp-sort-your-numbers-correctly.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --values10,000 | Values per test. |
| --seed0 | Random seed. |
Our run
Does byte order match the intended order?
| Encoding | Correct |
|---|---|
| integers, plain two's complement | no |
| integers, sign bit flipped | yes |
| floats, plain IEEE bytes | no |
| floats, sign rule | yes |
| composite keys, plain concatenation unique | no |
| composite keys, escaped + terminated unique | yes |
| composite keys, escaped + terminated order | yes |
integers, plain two's complement no
integers, sign bit flipped yes
floats, plain IEEE bytes no
floats, sign rule yes
composite keys, plain concatenation unique no
composite keys, escaped + terminated unique yes
composite keys, escaped + terminated order yes
('ab','c') and ('a','bc') concatenated: b'abc' b'abc' -> the same bytesWhat we found
Plain two's complement sorted integers wrongly (no); with the sign bit flipped, correctly (yes). Raw IEEE bytes sorted floats wrongly (no); with the sign rule, correctly (yes). Plain concatenation made composite keys collide (unique: no); escaped and terminated, they were unique (yes) and in order (yes).
Read it
An order-preserving encoding moves the comparison logic into the bytes, so the storage engine never needs to know the types. The cost is that the encoding is now part of the data's meaning: change it and every stored key sorts differently. The two failures are silent in different ways. Wrong sign handling puts negative values in the wrong place in every range scan, and unescaped composite keys make two different keys equal.