A bit pattern needs an interpretation. In an eight-bit unsigned word, 11111010 means 250. In an eight-bit one's-complement signed word it means −5. Neither binary nor hexadecimal spelling alone establishes the signed interpretation.
This lesson models a historical signed-integer representation using unsigned C++ storage. Modern C++ signed integers use two's complement; changing a type name does not switch the machine to one's complement. The model is useful for understanding representations and the end-around addition used in Internet checksums.
1. Encode a negative magnitude
First choose the word width. Write the positive magnitude, then invert every bit inside that width. In eight bits, +5 is 00000101 and −5 is 11111010, or 0xFA. There is no extra +1 step in one's complement.
The high bit separates nonnegative from negative encodings. To decode a negative code, invert the width's bits to recover its magnitude, then negate the magnitude. The range is symmetric: for w bits it is −(2^(w−1)−1) through +(2^(w−1)−1). Eight-bit values run from −127 through +127.
Do not apply unary minus to INT64_MIN while trying to obtain its magnitude. It has no positive int64_t counterpart. The one's-complement encode contract rejects it even at width 64 because it lies below that model's minimum. Validate the range before taking a negative magnitude.
2. Two encodings of zero
Inverting the all-zero word produces the all-one word. Both represent numeric zero: 00000000 is positive zero; 11111111 is negative zero. In a four-bit model, −0 is 1111, not 1000. That latter spelling belongs to sign-magnitude's negative zero and must not be confused with this representation.
The build's Decoded record contains an int64_t value and a bool negative_zero. Encoding the numeric value 0 produces positive zero. Decoding the all-one word preserves its negative-zero identity in the flag. A raw-word addition keeps whichever zero encoding the arithmetic produces, rather than silently normalizing it.
Bitwise complement (~) is not logical negation (!). Logical negation asks whether a value is zero and returns bool. Bitwise NOT inverts all value bits of its promoted operand. To flip only eight modeled positions in a wider unsigned value, XOR with the mask 0xFF or AND the complement with that mask.
#include <print>
int main() {
constexpr unsigned word = 5;
constexpr unsigned mask = 0xFF;
std::println("{:02X} {}", word ^ mask, !word);
}3. End-around carry changes addition
Add the raw words. If there is an outgoing carry, add it back into the least significant position. Consider four-bit 5 + (−3). The codes are 0101 and 1100. Their unsigned sum is 10001. Keep 0001, then add the outgoing 1, giving 0010, which represents +2. Simply discarding the carry would give the wrong value.
The zero result is instructive: four-bit 0101 + 1010, the codes for 5 and −5, produces 1111, the negative-zero code, without a carry. Meanwhile 0000 + 0000 produces positive zero. Distinguish numeric equality from identical bit patterns.
#include <print>
int main() {
constexpr unsigned a = 5, b = 12;
constexpr unsigned total = a + b;
constexpr unsigned folded = (total & 15u) + (total >> 4);
std::println("{} {}", total, folded);
}This algorithm describes a word arithmetic model. Like other fixed-width arithmetic, it does not make every mathematical signed sum representable. For instance, four-bit +7 plus +1 is outside −7…+7. The build returns a raw word and does not claim to be a checked signed-add API.
4. Model the full-width case safely
For widths below 64, two width-sized operands can be added in uint64_t and the outgoing carry read above the selected width. At width 64, unsigned addition is modulo 2^64. Detect the carry by comparing the wrapped sum with one operand, then add that carry to the low word. Never shift a uint64_t by 64 or try to store the mathematical value 2^64 in it.
The lab validates widths 2–64 and rejects raw bits outside the selected width. Exercise both zeros, a mixed-sign sum, maximum magnitudes, and a carry at width 64. Read the specification for exact error behavior.
RFC 1071 describes the Internet checksum's one's-complement sum and end-around carry. A complete checksum also has byte pairing, odd-length handling and final-complement rules; the lab here implements the underlying word operation, not that whole protocol. Next: two's complement.