Skip to content

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 of F8 + 10 is 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, and 0x10 lands 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 is FFF8, and FFF8 + 10 passes the top of the Short, so the 1 that would have reached the third byte is dropped and the second Short gets its own 0x10. 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 C2DF followed by a continuation byte in 80BF — 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

  1. 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.
  2. 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.
  3. 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.
  4. Type F8 FF 00 00 and Add 0x10 as Unsigned Short, then undo and do it as Unsigned Int. Compare the third byte.

See also