The Mysterious “M” in Your DDS: Naming Conventions vs Keyboard Shifts
This morning I opened a DSPF and spotted what looked like a data type of “M” against a field that smelled numeric. My first thought? “Since when did IBM i add an M type?” Turns out the original developer had a very correct reason for using ‘M’ and I had simply not seen it for so long (shhh… its been decades) and I had completely forgotten about this infrequently used datatype, so it got me digging deeper.
There really is an “M” data-type for number values in DSPF land. Just not where most people expect.
Let’s clear this up once and for all.
First, the Naming Convention Trap
In many older AS/400 shops we used prefixes to give fields instant meaning. You still see this everywhere:
- A# – Alphanumeric
- N# – Numeric
- D# – Date
- M# – Monetary / money amount
So you end up with perfectly normal DDS like this:
M#PRICE 9P 2
M#TAX 7P 2
M#TOTAL 11P 2
The “M#” is just part of the field name. The real data type is still P (packed decimal). No magic M type exists in DDS for physical or logical files.
But Wait – “M” Does Exist in DSPFs
Here’s where it gets fun. In Display Files (DSPF), M is a real thing, it is a keyboard shift attribute.
When you define a field in a DSPF, column 35 (the Usage/Keyboard Shift position in fixed-format DDS) lets you control what the user can actually type. One of those options is M.
M = Numeric-only character shift
This tells the display file: “Only let the user enter these characters”:
- Digits 0–9
- Plus sign (+)
- Minus sign (-)
- Comma (,)
- Period (.)
- Blank spaces
It does not make the field numeric for storage or calculations. The field is still defined as alphanumeric (A). M is purely an input filter.
The Real “M” : Keyboard Shift in DSPFs
In Display Files (DSPF), M is a genuine keyboard shift attribute.
It lives in column 35 (the Usage/Keyboard Shift position in fixed-format DDS) and controls exactly what the user can type into the field.
Valid Keyboard Shifts for Display Files
Here is the official list (IBM last updated 2025):
| Keyboard Shift | Meaning | Data Type Permitted |
|---|---|---|
| Blank | Default | Alphabetic only (Character) |
| A | Alphanumeric shift | Character |
| N | Numeric shift | Character or numeric |
| S | Signed numeric | Numeric |
| Y | Numeric only | Numeric |
| W | Katakana (Japan only) | Character |
| I | Inhibit keyboard entry | Character or numeric |
| D | Digits only | Character or numeric |
| M | Numeric only character | Character |
| F | Floating point | Numeric |
| L | Date | – |
| T | Time | – |
| Z | Timestamp | – |
Note: Data types J, E, O, and G are for DBCS support. G also handles UTF-16 and UCS-2.
What “M” Actually Does
When you code this:
A AMOUNT 15A B 10 5M
or with keywords:
A AMOUNT 15A B 10 5
A CHECK(MF)
The field accepts only digits 0-9, plus, minus, comma, period and spaces. It is still stored as alphanumeric (A). M is purely an input filter, not a numeric data type.
Bonus: DDS Data Types vs SQL Data Types
Because modern IBM i work often mixes DDS and SQL, here is a practical comparison of the most common types.
| DDS Type | Meaning | Typical Length/Format | SQL Equivalent | Notes / Gotchas |
|---|---|---|---|---|
| A | Alphanumeric (Character) | Up to 32,766 | CHAR or VARCHAR | VARCHAR is usually better in SQL tables |
| P | Packed decimal | 1-31 digits | DECIMAL(p,s) or NUMERIC(p,s) | Most common for money fields |
| S | Zoned decimal | 1-31 digits | DECIMAL(p,s) or NUMERIC(p,s) | Less efficient than packed |
| B | Binary | 1-9 bytes | SMALLINT / INTEGER / BIGINT | Use SQL integer types instead |
| F | Floating point | 4 or 8 bytes | FLOAT or DOUBLE | Rare on IBM i business apps |
| L | Date | 10 bytes (ISO) | DATE | Perfect match |
| T | Time | 8 bytes | TIME | Perfect match |
| Z | Timestamp | 26 bytes | TIMESTAMP | Perfect match |
| G | Graphic (DBCS) | 1-16,383 characters | GRAPHIC or VARGRAPHIC | For double-byte languages |
| J/E/O | DBCS open/either/only | Varies | VARGRAPHIC | Mostly legacy |
Light programmer humour moment: Mapping a 30-year-old DDS file packed with zoned decimals into a clean SQL table feels like teaching your grandad to use TikTok. Possible, but everyone ends up slightly confused.
Best practice tip: For brand new development, use SQL DDL with proper DECIMAL, DATE and VARCHAR types. Keep DDS for legacy files or when you need tight DSPF/PRTF control.
IBM SQL Data types against equivalent DDS data types
Here’s a practical side-by-side comparison of IBM i SQL (Db2 for i) data types versus their DDS equivalents. This focuses on what you’ll actually use when modernizing files or writing new tables on IBM i.
I will try to keep it technical, no fluff, but will probably add a bit of programmer humour because staring at packed decimals for 20 years does that to a man.
Numeric Types
| DDS Type | DDS Example | SQL Equivalent | SQL Example | Notes / Gotchas |
|---|---|---|---|---|
| Packed (P) | 7P 2 | DECIMAL(p,s) | DECIMAL(7,2) | Packed is the storage-efficient king in DDS. SQL DECIMAL is its direct match. Use this for money , it behaves like a sane accountant. |
| Zoned/Signed (S) | 7S 0 | NUMERIC(p,s) | NUMERIC(7,0) | Zoned decimal. SQL NUMERIC is the zoned equivalent. Takes more space than packed but displays nicely for humans. |
| Binary (B) | 4B 0 or 9B 0 or 18B 0 | SMALLINT / INTEGER / BIGINT | SMALLINT, INTEGER, BIGINT | DDS binary is basically the same as SQL integers under the hood. Note: Smallint=4 bytes, Integer=9 digits (4 bytes), Bigint=18 digits (8 bytes). |
| Float Single (F) | 9F FLTPCN(*SINGLE) | REAL or FLOAT(1-24) | REAL | Single precision floating point. Use only when you really need scientific notation and don’t mind the precision weirdness. |
| Float Double (F) | 17F FLTPCN(*DOUBLE) | DOUBLE or FLOAT(25-53) | DOUBLE | Double precision. Same warning as above, but with more digits of “oops”. |
| n/a | n/a | DECFLOAT(16) or DECFLOAT(34) | DECFLOAT(34) | No direct DDS equivalent. Decimal floating point, it’s excellent for exact decimal math without rounding surprises. Modern SQL win. |
Pro tip: In RPG when you externally describe an SQL table, SMALLINT becomes 4B 0, INTEGER becomes 9B 0, and BIGINT becomes 20I 0 (with EXTBININT(*YES) it gets friendlier). The precompiler has opinions.
Character & Binary String Types
| DDS Type | DDS Example | SQL Equivalent | SQL Example | Notes |
|---|---|---|---|---|
| Character (A) | 10A | CHAR(n) | CHAR(10) | Fixed length. Padded with blanks. Classic. |
| Variable Char (A + VARLEN) | 50A VARLEN | VARCHAR(n) | VARCHAR(50) | Variable length. Saves space and is the modern default. |
| Hex (H) | 10H | CHAR(n) FOR BIT DATA | CHAR(10) FOR BIT DATA | Binary character data. |
| Binary (5) | 10 5 (binary char) | BINARY(n) | BINARY(10) | Fixed binary. |
| Var Binary | 10 5 VARLEN | VARBINARY(n) | VARBINARY(10) | Variable binary. |
Date / Time Types (The Good Ones)
| DDS | DDS Example | SQL | SQL Example | Notes |
|---|---|---|---|---|
| Date (L) | L | DATE | DATE | ISO date by default. Beautiful. |
| Time (T) | T | TIME | TIME | Time. |
| Timestamp (Z) | Z | TIMESTAMP | TIMESTAMP | Timestamp with microsecond precision. |
SQL bonus: You get TIMESTAMPDIFF, CURRENT DATE, etc. DDS just sits there looking pretty.
Large Objects & Special Types
| DDS | SQL Equivalent | Notes |
|---|---|---|
| n/a | CLOB(n) | Character Large Object. Up to 2GB. No DDS equivalent (as far as I am aware) this is why we love SQL. |
| n/a | BLOB(n) | Binary Large Object. |
| n/a | DBCLOB(n) | Double-byte character LOB. |
| n/a | DATALINK | Still exists but rarely used. Links to external files. |
| Graphic (G) | GRAPHIC / VARGRAPHIC | UCS-2 / Unicode. Use CCSID(13488) for proper Unicode. |
| n/a | XML, JSON (via functions) | Modern SQL goodies DDS never dreamed of. |
Quick Mapping Summary (Most Common)
- Packed → DECIMAL (your go-to for business numbers)
- Zoned → NUMERIC
- Binary → SMALLINT/INTEGER/BIGINT
- A → CHAR / VARCHAR
- L/T/Z → DATE/TIME/TIMESTAMP
- H/5 → BINARY types
Modern advice from the IBM-i battle fields: If you’re creating anything new, use SQL DDL (CREATE TABLE). It gives you constraints, identity columns, long names, better performance with SQE, and future-proofing. DDS is still fine for legacy stuff and display/printer files (SQL can’t do those).
You can even use the QSQGNDDL API to auto-generate DDL from your existing DDS files, IBM literally hands you the conversion script.
If you’re creating/modifying/modernizing DSPF then remember:
- M# in a field name = old naming convention (usually monetary)
- M in column 35 of a DSPF = keyboard shift for numeric-only character input
- M does not change the data type, the field is still A unless you define it otherwise
- Always check the column position in fixed-format DDS
- Know your DDS-to-SQL mappings or you will spend hours chasing data format bugs
Legacy code is full of these little time bombs. One developer from years ago uses M# for money fields, another sticks an M in the keyboard shift column, and suddenly the next person is convinced IBM quietly added a new data type while we were not looking.
Got any favourite old-school IBM i naming conventions, quirky keyboard shifts, or painful DDS-to-SQL migration stories? Drop them in the comments. I love collecting these war stories.
Happy coding on the ‘i!


