Skip to content

Set Minimum

Level: 201 · for anyone with a hex editor open

One line: Set Minimum raises every value below the operand to the operand — a floor, which in code is max() — so Set Minimum 0x20 turns tabs, CRs and LFs into spaces and leaves UTF-8 alone, until Treat Data As says Signed Byte, where every byte UTF-8 uses outside ASCII is negative and becomes a space as well.

What the dialog does

The manual defines it in words rather than C: if X[i] is less than the operand, X[i] is set to the operand (Hex Operations). "Less than" compares numbers, and a byte is a number only once Treat Data As has said how to read it. That puts Set Minimum among the five operations in the chapter's table whose bytes depend on the sign.

In Python

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

1. SET MINIMUM 20 ON A LINE OF TEXT
------------------------------------------------------------------------
   A, a tab, B, CR, LF, then café. Set Minimum 0x20 raises every value
   below a space to a space:

     the bytes       41 09 42 0d 0a 63 61 66 c3 a9
     Unsigned Byte   41 20 42 20 20 63 61 66 c3 a9   'A B  café'
     Signed Byte     41 20 42 20 20 63 61 66 20 20   'A B  caf  '

   As Unsigned Byte the tab, the CR and the LF became spaces, and café
   came through. As Signed Byte, C3 and A9 are -61 and -87, both below
   32, so the é became two spaces as well.

2. HOW MANY BYTE VALUES IT CHANGES
------------------------------------------------------------------------
     Unsigned Byte    32 of 256   00-1f
     Signed Byte     160 of 256   00-1f, 80-ff

   The C0 control characters are 00-1F under both settings. Signed
   Byte adds every byte from 80 up -- which is every byte UTF-8 uses
   for anything outside ASCII.

3. THE NAME IS THE FLOOR, NOT THE FUNCTION
------------------------------------------------------------------------
   Set Minimum does not take a minimum. It sets one, which in Python is
   max():

     Set Minimum 20 on 10 -> 20      max(0x10, 0x20) -> 20
     Set Minimum 20 on 20 -> 20      max(0x20, 0x20) -> 20
     Set Minimum 20 on 41 -> 41      max(0x41, 0x20) -> 41

4. ON UTF-16, THE WIDTH DECIDES WHAT A CONTROL CHARACTER IS
------------------------------------------------------------------------
   A, tab, B in UTF-16LE is 41 00 09 00 42 00. Set Minimum 0x20:

     Unsigned Short  41 00 20 00 42 00   .decode('utf-16-le') -> 'A B'
     Unsigned Byte   41 20 20 20 42 20   .decode('utf-16-le') -> '⁁†⁂'

   As a Short, only the tab was below 0x20. As a Byte, so was every 00
   that UTF-16 puts beside an ASCII letter, and three new characters
   arrived:

     U+2041  CARET INSERTION POINT
     U+2020  DAGGER
     U+2042  ASTERISM

A floor under a line of text

Section 1 runs Set Minimum 0x20 over A, tab, B, CR, LF, café. As Unsigned Byte the only bytes below 20 are the three control characters, and they become spaces — a crude but real way to flatten a field that must not contain a line break. As Signed Byte, é's bytes C3 A9 are −61 and −87, both below 32, and they become spaces too. Section 2 counts it for all 256 values: 32 change as Unsigned Byte, 00 to 1F, and 160 as Signed Byte — the same 32 plus every byte from 80 up, which is every byte UTF-8 uses for a character outside ASCII.

The name is the floor, not the function

The name sounds like a request to take a minimum, and it does the opposite: it sets one, so the result is the larger of the value and the operand. That is max(), which section 3 prints beside it. Set Maximum is the ceiling, and min().

What a value is decides what is below the floor

Section 4 runs the same floor over A, tab, B in UTF-16LE. As Unsigned Short each character is one value, only the tab is below 0x20, and the result is A B. As Unsigned Byte, every 00 beside an ASCII letter is below 0x20 too, and the three pairs decode to U+2041 CARET INSERTION POINT, U+2020 DAGGER and U+2042 ASTERISM. The floor did what it was asked. It was asked about bytes, and UTF-16 text is not made of byte-sized characters.

What the manual does not say

  • Whether an operand the type cannot hold is compared as typed or after conversion to the type.
  • What Set Minimum does with a NaN on Float or Double.

If you are coming from Python or ABAP

Python. bytes(max(b, 0x20) for b in data) is section 1's unsigned row. The signed row needs the reading spelled out — struct.pack(f'{n}b', *(max(v, 0x20) for v in struct.unpack(f'{n}b', data))) — which is this page's argument in one line: a Python bytes object is unsigned, so a comparison on its items is an unsigned comparison unless you unpack them as something else. For text, str.translate() with a table of the control characters is the tool that knows which bytes are characters.

ABAP. (Not machine-checked — CI cannot run ABAP.) The floor is nmax( ), which returns the greatest of two to nine numeric arguments: nmax( val1 = value val2 = 32 ). A byte field has to become a number before it can be compared, and a one-byte x field converts to i padded on the left with zero bytes ↗, so C3 compares as 195 — the Unsigned Byte reading — on every server.

Try it

  1. Copy a CSV with a quoted field that spans lines, select that field, and Set Minimum 0x20 as Unsigned Byte. Open both files in a spreadsheet and count the rows.
  2. Repeat on an accented line as Signed Byte, and count how many spaces each accented letter became.
  3. Open a UTF-16LE file, Set Minimum 0x20 as Unsigned Short, then undo and do it as Unsigned Byte, reading both with View > Character Set on Unicode.
  4. Try an operand of 128 as Signed Byte, which that type cannot hold, and note what the dialog does.

See also

  • Set Maximum — the ceiling, and the other half of a range
  • Control characters — the 32 bytes below the floor in section 2
  • Swap Bytes — another operation that treats UTF-16 as bytes if you let it
  • Divide — the first operation in the list that needed the sign