6

I'm just curious if there was ever other instructions that were more ASCII-centric than the CISC associated ones we're left with now?

Specifically, all the Binary Coded Decimal (BCD) instructions currently labeled "ASCII",

  • AAA : ASCII Adjust after Addition
  • AAS : ASCII Adjust after Subtraction
  • AAM : ASCII Adjust after Multiplication
  • AAD : ASCII Adjust before Division

And even defined as "ASCII Adjust"?

  • 1
    Are you asking about other instructions on x86, or on any architecture? (As a side-note, all these instructions were removed from x86-64.) IMO the “ASCII” instructions are rather poorly named, they’re about BCD rather than ASCII. – Stephen Kitt Sep 24 '18 at 6:02
  • But no rhyme or reason to calling them ASCII, there was no more of them that were ASCII specific? It just seems most of this stuff has a good reason albeit something obscure and legacy-related. This one is because Intel engineers didn't know what ASCII was? – Evan Carroll Sep 24 '18 at 7:53
  • 1
    The ASCII adjust is actually what is missing from these instructions... – tofro Sep 24 '18 at 10:53
  • 1
    'A' would be consistent naming with the related flag, which was variously called A, AF, auxiliary carry, and adjust flag. This flag also existed on the 8085, but not the related instructions. – Dr Sheldon Sep 24 '18 at 15:19
8

The “ASCII” instructions are really about unpacked BCD, not ASCII. There is some justification for calling them “ASCII”, because unpacked BCD can easily be converted to and from ASCII, and in fact if only the lower nibble is considered, ASCII numerals are encoded using the exact same values as their unpacked BCD counterparts. The 8086 Primer explains the naming thus:

The instructions that perform these four adjustments are called ASCII adjustment instructions (because ASCII is the most common example of unpacked BCD)

but the instructions sequences involved in unpacked BCD arithmetic don’t deal with unpacked values whose top nibble is non-zero consistently, and the explanation thus ends up being more confusing than useful (and might explain why so many authors got this wrong). A better expansion might have been “accumulator adjust” instructions (as used e.g. in Intel Microprocessors: 8008 to 8086 by Stephen P. Morse et al) although that doesn’t help understand the nature of the adjustment. The ease of conversion to and from ASCII results from the fact that numerical digits are encoded as values from 48 (for 0) to 57 (for 9), i.e. 48 + digit, and that 48 happens to fit entirely in the top nibble of a byte.

Let’s work through an example, first as unpacked BCD, then as ASCII.

As unpacked BCD, to multiply 7 by 6, we’d store 7 in AL, and 6 in BL (since they’re single-digit values in decimal, there is no transformation involved). Integer multiplication (MUL BL) results in 7×6 in AX. AAM then splits AX into two unpacked BCD values in AH and AL, 4 and 2 respectively.

As ASCII, we’d start with 55 in AL and 54 in BL (the ASCII codes for “7” and “6”). The rules for the ASCII adjust after multiplication instruction say that values should have their top nibble cleared; doing this to AL and BL results in ... 7 in AL and 6 in BL (magic!). Then the same sequence of operations as above results in 4 and 2 in AH and AL; to convert that to ASCII, we can OR each value with 48, or better yet, OR AX with 12336 (3030h), resulting in 52 in AH and 50 in AL, the ASCII codes for “4” and “2”.

Note that for multiplication and division, the top nibble must be zero, but that requirement doesn’t apply for addition and subtraction, so addition and subtraction can be performed on ASCII values directly (but you still need to convert the results back to ASCII).

It’s also interesting to note that AAM and AAD, as implemented, are generic 8-bit multiply and divide instructions: AAD multipies AH by its 8-bit immediate, adds AL, and stores the result in AL (so as defined, AL ← AH × 10 + AL, AH ← 0); AAM divides AL by its 8-bit immediate and stores the quotient in AH and the remainder in AL (as defined, AH ← AL ÷ 10, AL ← AL mod 10).

There aren’t any other x86 instructions dedicated to BCD operations. One of the x86 flags is BCD specific, AF (the auxiliary carry flag, which reflects carry or borrow on the lower nibble only). The x87 supports packed BCD operations.

  • 1
    You could argue that if named properly, the AAD opcode would have ended up in BAD - Maybe they wanted to evade that ;) – tofro Sep 24 '18 at 10:45
  • 1
    @tofro hah yes, I like that — or even UBAD! BAA and BAM would have been amusing too. – Stephen Kitt Sep 24 '18 at 11:03
  • No worse than IBM's naming of one of the S/360 assembler instructions "Execute Operator". (After too many operator-error-induced aborted runs, I often wished that one actually did what it said on the can! – alephzero Sep 24 '18 at 13:00
2

On the 8086, an 8-bit quantity can represent a number in four ways. Of these four, unpacked BCD is the closest to direct representation by ASCII. The AAx instructions are needed to use this representation.

Unsigned binary number

  • Bit format: 8 binary digits dddddddd
  • Represents values from 0 to 255.
  • Addition: ADD (or ADC).
  • Subtraction: SUB (or SBB).
  • Multiplication: MUL.
  • Division: DIV.

Signed binary number

  • Bit format: Sign bit s and data bits sddddddd
  • Represents values from -128 to +127.
  • Addition: ADD (or ADC).
  • Subtraction: SUB (or SBB).
  • Multiplication: IMUL.
  • Division: IDIV.

Packed BCD

  • Bit format: hhhhllll -- one decimal digit in the high nibble hhhh and another decimal digit in the low nibble llll.
  • Represents values from 00 to 99.
  • Addition: ADD (or ADC), then DAA.
  • Subtraction: SUB (or SBB), then DAS.
  • Multiplication: Not directly supported.
  • Division: Not directly supported.

Unpacked BCD

  • Bit format: 0000llll -- one decimal digit in the low nibble.
  • Represents values from 0 to 9.
  • Can be coverted to/from its ASCII representation by adding/subtracting 30h.
  • Addition: ADD (or ADC), then AAA. This automatically zeroes the high nibble of the result.
  • Subtraction: SUB (or SBB), then AAS. This automatically zeroes the high nibble of the result.
  • Multiplication: MUL, then AAM.
  • Division: AAD first, then DIV.
1

Preface: Before writing anything that I got it wrong, it might be helpful to take a look at the original documentations instead of refering some web page, as there are TONS out there who got that part wrong.


I'm just curious if there was ever other instructions that were more ASCII-centric than the CISC associated ones we're left with now?

Sure, while the Intel ones still left the number without its zone(*1) part, the IBM /360 did even add that in their unpackign instrucktions accoding to the mode set (ASCII or EBCDIC)

Specifically, all the Binary Coded Decimal (BCD) instructions currently labeled "ASCII", [AAA/AAS/...]

And even defined as "ASCII Adjust"?

Ok, this is maybe a tiny bit missleading, as they are not so much codeset specific, as they are about single byte BCD numbers. But then again, it's what is needed after adding/subracting/etc. two ASCII numbers. Last but not least the much nicer 'DECIMAL ADJUST x' names where already taken.

The 8086 supports two set of adjust instructions the DAx which are used to normalize the result of a binary add of single byte packed BCD numbers, that is two decimal digits per byte, stored in each nibble. It's what most people usually see as BCD.

But then there are also single byte BCD numbers, where only the lower nibble of a byte contains a BCD digit. The higher nibble may contain anything as it's not regarded useful. When some common character encoding is used it may contain a zone - like 3 in case of ASCII of F when it's about EBCDIC. The AAx instructions are ment to adjust (correct) the result of an operation on such single digit BCD numbers after being done in binary fashion.

With these instructions it is possible two ASCII numbers (30h..39h) without any prior conversion and adjust the result. It will yield a valid BCD number in the lower nibble of AL, while the higher is cleared. The get a valid ASCII number again 30h needs to be ORed onto AL ant the result sstored in an ASCII string again (*2). Similar for the other operations.

These adjust operations allow direkt arithmetics on ASCII strings without prior conversion when done in a loop arbitary length ASCII strings can be handled - and by using string operation this gets quite performant.

So while they are not exactly produceing ASCII, they do provide what's needed to directly calculate with ASCII numbers.


*1 - While ASCII is a collection of various characters without much structure, IBM did organize it's 8 bit EBCDIC in a way that the upper nibble clearly defines what kind a character is, control, uppercase alpha, lower case alpha, symbol, number. That Nibble is called a 'Zone' as it defines the 'Zone' a code (character) belongs to.

For example the F- zone consists only of numbers 0..9, no other characters (no, FF is not a character but high value mark).

*2 - For example like this code to add two strings of up to 32768 ASCII digits (STRING2 onto STRING1)

    MOV   SI,OFFSET STRING2
    MOV   DI,OFFSET STRING1
    MOV   CX,STRINGLEN
    CLD
    CLC
LP:
    LODSB         * Get one (ASCII) number from STRING2
    ADC   AL,[DI] * Add one (ASCII) number from STRING1
    AAA           * Correst it for decimal overflow
    LAHF          * Flags sichern
    OR    AL,30h  * Make it ASCII again
    SAHF          * Restore flags
    STOSB         * Resulting ASCII Character to STRING1
    LOOP  LP      * Go for it Tiger.
  • 2
    Not all AAx instructions can deal with ASCII digits; AAM and AAD require the top nibble to be 0, they don’t ignore it. Quoting the manual, p. 2-34: “the high-order half-byte of an ASCII numeral must be set to 0H prior to multiplication or division.” I also checked by running code on a 8086, and as you’d expect MUL multiplies the full bytes, not ignoring the upper nibble, and AAM can’t make up for the difference. Likewise for AAD, the other way round. – Stephen Kitt Sep 24 '18 at 20:02
  • @StephenKitt True, they need to have the zone part nulled out before using multiply/division. But the basic fact stays that they are ment to work on a data type where each (decimal) digit is in a seperate byte, thus without much hassles interchangable with character based encoding (at least where the characters representing 0..9 also encode so in the lower nibble). Which does differ from what is commonly called BCD, where two digits are packed in one byte. And that's the difference the naming is based on. – Raffzahn Sep 24 '18 at 20:22
0

Historical Prospective

You can see this mentioned on the 8086 Primer page 58

One example of unpacked BCD is the ASCII representation of digits. ASCII is a 7-bit representation of a set of characters. The ASCII representation of digits are shown in Table 3.6 The four most significant bits contain 0011, which is not relevant to the digit value.

I looked it up in the 4004 and the 8008 (MCS-8) and neither of these mention these instructions or any other instructions about ASCII.

  • They were introduced on the 8086 – cup Sep 24 '18 at 11:29
  • 1
    That quote is from the 8086 Primer, and it ends up being rather misleading. The definition of unpacked BCD used there is inconsistent: the quote suggests that only the four lower bits matter, but not all of the AA instructions ignore the top four bits. ASCII being an example of unpacked BCD is only valid if the top nibble is consistently ignored. – Stephen Kitt Sep 24 '18 at 11:42
  • 1
    The 8080 brought the DAx instructions handling two digit packed BCD numbers to the 8086, thus new names for the single digit instructions where needed. – Raffzahn Sep 24 '18 at 16:26

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.