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¶
- Copy a CSV with a quoted field that spans lines, select that field, and Set Minimum
0x20as Unsigned Byte. Open both files in a spreadsheet and count the rows. - Repeat on an accented line as Signed Byte, and count how many spaces each accented letter became.
- Open a UTF-16LE file, Set Minimum
0x20as Unsigned Short, then undo and do it as Unsigned Byte, reading both with View > Character Set on Unicode. - 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