Add¶
Level: 201 · for anyone with a hex editor open
One line: X[i] += Operand adds once per value, and a carry travels only as far as the edge of the value it started in — so Add 0x10 on F8 00 writes 08 10, 08 01 or F8 10 depending on the type and the byte order, and a carry a four-byte Int would have kept is simply gone from two Shorts.
What the dialog does¶
The manual writes Add as X[i] += Operand (Hex Operations ↗). It is the first operation in the list that reads the value it changes, and so the first where the value's width shows: a sum can need more bits than either number added, and the extra has to go somewhere. Inside a value it carries into the next byte up, as a carry does on paper. At the value's edge there is nowhere left to go, and the programs here keep the low bits — the one assumption the chapter page makes that the manual does not state.
In Python¶
Verified output of add_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. THE SAME BYTES, THREE SUMS
------------------------------------------------------------------------
Add 0x10 to f8 00:
Unsigned Byte 08 10 F8+10 is 108, and a byte keeps 08
Unsigned Short, little 08 01 00F8+10 is 0108: the 1 carries
Unsigned Short, big f8 10 F800+10 is F810: nothing carries
2. THE CARRY NEVER REACHES THE NEXT VALUE
------------------------------------------------------------------------
Add 0x10 to f8 ff 00 00, little-endian, as two Shorts and as one Int:
Unsigned Short 08 00 10 00 FFF8+10 keeps 0008; the next Short gets its own 10
Unsigned Int 08 00 01 00 0000FFF8+10 is 00010008
One setting, one byte apart, and the 1 that the Int kept in its
third byte is simply gone from the Short.
3. SIGNED OR UNSIGNED, THE SAME BYTES
------------------------------------------------------------------------
Every byte value, every operand from 0 to 127, both settings:
wrote the same byte 32,768 of 32,768
7F + 1 is 80 either way. Read back, that byte is 128
as an Unsigned Byte and -128 as a Signed one: the bytes agree,
and only the number they are read as moved.
4. ADD 13 TO café
------------------------------------------------------------------------
café in UTF-8 is 63 61 66 c3 a9. Add to every byte, as Unsigned Byte:
+ 1 64 62 67 c4 aa 'dbgĪ'
+ 13 70 6e 73 d0 b6 'pnsж'
+ 64 a3 a1 a6 03 e9 not UTF-8
é is C3 A9: a lead byte from C2-DF and a continuation byte from
80-BF. Add 1 or 13 and both stay inside their ranges, so the pair
is still one character, just a different one. Add 64 and neither
does.
5. OPERAND STEP ADDS A DIFFERENT AMOUNT TO EACH VALUE
------------------------------------------------------------------------
Add 0 with Operand Step 1 adds 0 to the first value, 1 to the
second, and so on:
b'AAAA' -> b'ABCD'
Where the carry goes¶
Sections 1 and 2 are four answers to one question. F8 plus 10 is 108, which takes nine bits:
- As Unsigned Byte, each byte is a value and gets its own
0x10, and the ninth bit ofF8 + 10is dropped at the byte's edge:08 10. - As a little-endian Unsigned Short, the two bytes are one value,
0x00F8, low byte first. The ninth bit carries into the second byte:08 01. - As a big-endian Unsigned Short, the value is
0xF800, and0x10lands on its low byte, which is the second one. Nothing carries:F8 10. - As two Shorts against one Int, over
F8 FF 00 00. The first Short isFFF8, andFFF8 + 10passes the top of the Short, so the 1 that would have reached the third byte is dropped and the second Short gets its own0x10. The Int keeps that 1 in its third byte.
That is the sense in which a width is part of the data. Arithmetic has its own width makes the general case; this is the case as a menu item.
Signed or unsigned writes the same bytes¶
Section 3 adds every operand from 0 to 127 to every byte, once as Unsigned Byte and once as Signed Byte, and the two wrote the same byte all 32,768 times. Two's complement is built for exactly this: adding to a signed value and adding to its unsigned reading are the same operation on the bits, and only the reading of the result differs — 7F + 1 is 80, which is 128 as an Unsigned Byte and −128 as a Signed one. The chapter's table finds only five of the twenty that are known to care.
Add 13 to text¶
Section 4 adds to café in UTF-8. Add 1 gives dbgĪ and Add 13 gives pnsж, and both are still valid UTF-8, because é is C3 A9 — a lead byte in C2–DF followed by a continuation byte in 80–BF — and adding 1 or 13 keeps each inside its range. The pair still spells one character, just not the same one. Add 64 pushes both out, and the result is not UTF-8 at all. That is the difference between ROT13, which rotates within the alphabet and wraps at 26, and Add, which does arithmetic on bytes and wraps only at 256.
What the manual does not say¶
- What happens to a result that does not fit its type. Every output above keeps the low bits.
- Whether Operand Step can carry the operand past the top of the type.
- What becomes of a last value too short for the type.
If you are coming from Python or ABAP¶
Python. Python's integers have no width, so the dialog's silent step is one you write: bytes((b + k) & 0xFF for b in data) is Add as Unsigned Byte, and struct.iter_unpack('<H', data) followed by struct.pack('<H', (v + k) & 0xFFFF) is the little-endian Short. Leave the mask off and Python refuses instead of wrapping — a bytearray with ValueError, an array('B') with OverflowError, two different exceptions for the same byte that did not fit.
ABAP. (Not machine-checked — CI cannot run ABAP.) ABAP adds with the opposite policy: in an integer calculation, a subtotal outside the type's range raises CX_SY_ARITHMETIC_OVERFLOW ↗ instead of keeping the low bits. To add to a byte field you go through an integer, and both conversions behave like the dialog's Big Endian: an x field is read as a big-endian number ↗, and an i written back into a shorter x field is cut on the left ↗, which keeps its low bytes the way the dialog does.
Try it¶
- Copy a PNG, select the four bytes at offset 16 — the image width, stored big-endian — and Add 1 as Unsigned Int with Big Endian. Watch the Inspector. Undo, repeat with Little Endian, and see which byte moved instead.
- Select a line of UTF-8 text with accents in it and Add 13 as Unsigned Byte. Find a character that is no longer valid and work out, from its bytes, which one left its range.
- Make a scratch file of three fixed-size records, each starting with a four-byte counter, and bump every counter at once: Add 1 as Signed Int with Skip Bytes set to the rest of the record.
- Type
F8 FF 00 00and Add0x10as Unsigned Short, then undo and do it as Unsigned Int. Compare the third byte.
See also¶
- Subtract — the same wrap, from below
- Arithmetic has its own width — why a sum can outgrow the bytes that hold it
- Rotation is not encryption — ROT13, an addition that wraps at 26
- UTF-8 by hand — the lead and continuation ranges section 4 stays inside