Skip to content

Binary Or

Level: 201 · for anyone with a hex editor open

One line: X[i] |= Operand sets every bit the operand has and never clears one, so Or 20 lowercases ASCII letters and leaves digits alone, turns every two-byte UTF-8 lead byte into the lead byte of a longer character, and — because two inputs can reach the same output — cannot be undone by anything.

What the dialog does

The manual writes Binary Or as X[i] |= Operand (Hex Operations). Each bit of the result is 1 wherever the value or the operand has a 1. Like Binary And it is a mask with no carries, so the sign does not matter and the byte order of a wider mask does.

In Python

Verified output of binary_or_py.py — regenerated by tools/run_examples.py, never hand-typed.

1. OR 20 LOWERCASES ASCII LETTERS, AND A FEW THINGS BESIDES
------------------------------------------------------------------------
   Bit 5 is the difference between an ASCII capital and its lowercase
   letter. Set it everywhere:

     b'HI [42]@' -> b'hi {42}`'

   The space and the digits live in 20-3F, where that bit is already
   set, so they are untouched -- unlike Subtract 20, which takes 0x20
   from every byte whether it has the bit or not. [ ] and @ live in
   40-5F with the capitals, and became { } and `.

2. ON UTF-8, IT BREAKS EVERY TWO-BYTE CHARACTER
------------------------------------------------------------------------
     before   43 41 46 c3 89   'CAFÉ'
     after    63 61 66 e3 a9   not UTF-8

   Two-byte lead bytes, C2-DF, that Or 20 changes: 30 of 30,
   every one into E2-FF: True

   A lead byte from E0 up announces a character of three bytes or more,
   so C3 | 20 = E3 tells a decoder to expect two continuation bytes,
   and the É had only one.

3. OR NEVER CLEARS A BIT, SO IT CANNOT BE UNDONE
------------------------------------------------------------------------
     different bytes Or 20 can write   128 of 256
     41 and 61 both become             61 and 61

   Once two bytes have been merged into one, no operation can tell
   which one it was. And and Or both lose information this way; Xor and
   Invert never do.

4. OR 80 MOVES EVERY BYTE INTO THE TOP HALF
------------------------------------------------------------------------
   b'Hi!' is 48 69 21. Or 0x80:

     c8 e9 a1   as Latin-1 'Èé¡', and not UTF-8

   Every ASCII character lands exactly 0x80 higher, in the half of the
   byte range where each code page keeps its own letters -- which is
   why text that has been through Or 80 reads as accented garbage in
   one table and as nothing at all in UTF-8.

Or 20 lowercases letters, and a little more

Bit 5, 0x20, is the difference between an ASCII capital and its lowercase letter, and section 1 sets it everywhere in HI [42]@. The letters became lowercase. The space and the digits sit in 203F, where that bit is already set, so they did not change — which is the difference from Subtract 0x20, which takes 0x20 from every byte whether the bit is there or not. But [, ] and @ sit in 405F alongside the capitals, and became {, } and a backtick. A bit does not know which bytes are letters any better than arithmetic does.

On UTF-8, it breaks every two-byte character

Section 2 lowercases CAFÉ and gets bytes that are not UTF-8. The É is C3 89; Or 20 makes the lead byte E3, and a lead byte from E0 up announces a character of three bytes or more, so the decoder expects two continuation bytes and finds one. The program checks all 30 lead bytes of two-byte characters, C2DF, and Or 20 moves every one into E2FF. Every accented Latin letter, every Greek and Cyrillic letter, is two bytes in UTF-8, so every one of them breaks.

Or cannot be undone

Section 3 counts what Or 20 can write: 128 of the 256 byte values, and 41 and 61 both become 61. Once two bytes have been merged into one, no operation can tell which one was there. Binary And loses information the same way; Binary Xor and Binary Invert never do. Section 4 is the same one-way street at the top bit: Or 80 moves every ASCII byte exactly 0x80 higher, into the half of the byte range where each code page keeps its own letters — Hi! reads as Èé¡ in Latin-1 and as nothing at all in UTF-8.

What the manual does not say

  • Whether Binary Or is offered for Float and Double, where C has no |.
  • What becomes of a last value too short for the type.

If you are coming from Python or ABAP

Python. bytes(b | 0x20 for b in data) is section 1. The safe lowercase is bytes.lower(), which changes only the ASCII capitals: b'HI [42]@'.lower() keeps its brackets and its @, and 'CAFÉ'.encode().lower() leaves the É's two bytes untouched, so the result is still valid UTF-8 — only not lowercase. For text that is not ASCII, lowercase the str.

ABAP. (Not machine-checked — CI cannot run ABAP.) BIT-OR takes byte-like operands only ↗, and the keyword documentation's own example of it, '0011' BIT-OR '0101' giving 0111, is section 1's rule on two two-byte masks written as xstring literals. As with BIT-AND, the operand is bytes in the order you wrote them, so there is no byte order to choose.

Try it

  1. Or 20 over a copy of an all-capitals ASCII file, then search for {, }, | and backticks that were not there before.
  2. Or 20 over a UTF-8 line of accented capitals and count how many characters the editor's UTF-8 view can no longer show.
  3. Or 80 over a short ASCII word, then step through View > Character Set and note what each table makes of the result.
  4. Set a flag bit in every record of a file of fixed-size records: Or 0x01 as Unsigned Byte, with Skip Bytes set to the rest of each record.

See also

  • Binary And — the mask that clears bits
  • Binary Xor — the mask that flips bits, and can be undone
  • Subtract — changing case by arithmetic instead of by a bit
  • UTF-8 by hand — what a lead byte from E0 up announces