Binary coded decimal: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added Wren)
m (→‎{{header|Wren}}: Added libheaders.)
Line 97: Line 97:


=={{header|Wren}}==
=={{header|Wren}}==
{{libheader|Wren-check}}
{{libheader|Wren-math}}
{{libheader|Wren-str}}
{{libheader|Wren-fmt}}
In Wren all numbers are represented by 64 bit floats and the language has no real concept of bytes, nibbles or even integers.
In Wren all numbers are represented by 64 bit floats and the language has no real concept of bytes, nibbles or even integers.



Revision as of 10:37, 12 July 2022

Binary coded decimal is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Binary-Coded Decimal (or BCD for short) is a method of representing decimal numbers by storing what appears to be a decimal number but is actually stored as hexadecimal. Many CISC CPUs (e.g. X86 Assembly have special hardware routines for displaying these kinds of numbers.) On low-level hardware, such as 7-segment displays, binary-coded decimal is very important for outputting data in a format the end user can understand.

Task

Use your language's built-in BCD functions, OR create your own conversion function, that converts an addition of hexadecimal numbers to binary-coded decimal. You should get the following results with these test cases:

  •   0x19 + 1 = 0x20
  •   0x30 - 1 = 0x29
  •   0x99 + 1 = 0x100
Bonus Points

Demonstrate the above test cases in both "packed BCD" (two digits per byte) and "unpacked BCD" (one digit per byte).



6502 Assembly

Doesn't work with: Ricoh 2A03

The 6502 is a bit different in that it has a special operating mode where all addition and subtraction is handled as binary-coded decimal. Like the 68000, this must be invoked ahead of time, rather than using the Intel method of doing the math normally and then correcting it after the fact. (This special operating mode won't work on the aforementioned Ricoh 2A03, which performs math in "normal" mode even if the decimal flag is set.)

<lang 6502asm>sed ;set decimal flag; now all math is BCD lda #$19 clc adc #1 cld ;chances are, PrintHex won't work properly when in decimal mode. JSR PrintHex ;unimplemented print routine JSR NewLine

sed lda #$30 sec sbc #1 cld jsr PrintHex JSR NewLine

sed lda #$99 clc adc #1 pha lda #0 adc #0 ;adds the carry cld jsr PrintHex pla jsr PrintHex jsr NewLine rts ;return to basic</lang>

Output:
20
29
0100

68000 Assembly

The 68000 has special mathematics commands for binary-coded decimal. However, they only work at byte length, and cannot use immediate operands. Even adding by 1 this way requires you to load 1 into a register first. <lang 68000devpac> MOVEQ #$19,D0 MOVEQ #1,D1 MOVEQ #0,D2

ABCD D1,D0 JSR PrintHex JSR NewLine

MOVEQ #$30,D0 SBCD D1,D0 JSR PrintHex JSR NewLine

MOVE.B #$99,D0 ABCD D1,D0 ;D0 has rolled over to 00 and set both the extend and carry flags. ADDX D2,D2 ;add the extend flag which was set by the above operation ;this can't use immediate operands either so we're using D2 which we set to zero at the start.

MOVE.L D0,D3 ;back up the output since PrintHex takes D0 as its argument. MOVE.L D2,D0 ;print the 01 JSR PrintHex MOVE.L D3,D0 ;then the 00 JSR PrintHex

       jmp *</lang>
Output:
20
29
0100

J

Here, we represent hexadecimal numbers using J's constant notation, and to demonstrate bcd we generate results in that representation:

<lang J> bcd=: &.((10 #. 16 #.inv ". ::]) :. ('16b',16 hfd@#. 10 #.inv ]))

  16b19 +bcd 1

16b20

  16b30 -bcd 1

16b29

  16b99 +bcd 1

16b100

  (16b99 +bcd 1) -bcd 1

16b99</lang>

Wren

Library: Wren-check
Library: Wren-math
Library: Wren-str
Library: Wren-fmt

In Wren all numbers are represented by 64 bit floats and the language has no real concept of bytes, nibbles or even integers.

The following is therefore a simulation of BCD arithmetic using packed binary strings to represent decimal digits. It only works for non-negative integral numbers.

We can change to 'unpacked' notation simply by prepending '0000' to each 'digit' of the 'packed' notation.

In what follows, the hex prefix '0x' is simply a way of representing BCD literals and has nothing to do with hexadecimal as such. <lang ecmascript>import "./check" for Check import "./math" for Int import "./str" for Str import "./fmt" for Fmt

class BCD {

   static init_() {
       __bcd = [
           "0000", "0001", "0010", "0011", "0100",
           "0101", "0110", "0111", "1000", "1001"
       ]
       __dec = {
           "0000": "0", "0001": "1", "0010": "2", "0011": "3", "0100": "4",
           "0101": "5", "0110": "6", "0111": "7", "1000": "8", "1001": "9"
       }
   }
   construct new(n) {
       if (n is String) {
           if (n.startsWith("0x")) n = n[2..-1]
           n = Num.fromString(n)
       }
       Check.nonNegInt("n", n)
       if (!__bcd) BCD.init_()
       _b = ""
       for (digit in Int.digits(n)) _b = _b + __bcd[digit]
   }
   toInt {
       var ns = ""
       for (nibble in Str.chunks(_b, 4)) ns = ns + __dec[nibble]
       return Num.fromString(ns)
   }
   +(other) {
       if (!(other is BCD)) other = BCD.new(other)
       return BCD.new(this.toInt + other.toInt)
   }
   -(other) {
       if (!(other is BCD)) other = BCD.new(other)
       return BCD.new(this.toInt - other.toInt)
   }
   toString {
       var ret = _b.trimStart("0")
       if (ret == "") ret = "0"
       return ret
   }
   toUnpacked {
       var ret = ""
       for (nibble in Str.chunks(_b, 4)) ret = ret + "0000" + nibble
       ret = ret.trimStart("0")
       if (ret == "") ret = "0"
       return ret
   }
   toHex { "0x" + this.toInt.toString }

}

var hexs = ["0x19", "0x30", "0x99"] var ops = ["+", "-", "+"] for (packed in [true, false]) {

   for (i in 0...hexs.count) {
       var op = ops[i]
       var bcd = BCD.new(hexs[i])
       var bcd2 = (op == "+") ? bcd + 1 : bcd - 1
       var str = packed ? bcd.toString : bcd.toUnpacked
       var str2 = packed ? bcd2.toString : bcd2.toUnpacked
       var hex = bcd.toHex
       var hex2 = bcd2.toHex
       var un = packed ? "" : "un"
       var w = packed ? 8 : 12
       var args = [hex, op, hex2, un, w, str, op, str2]
       Fmt.lprint("$s $s 1 = $-5s or, in $0spacked BCD, $*s $s 1 = $s", args)
   }
   if (packed) System.print()

}</lang>

Output:
0x19 + 1 = 0x20  or, in packed BCD,    11001 + 1 = 100000
0x30 - 1 = 0x29  or, in packed BCD,   110000 - 1 = 101001
0x99 + 1 = 0x100 or, in packed BCD, 10011001 + 1 = 100000000

0x19 + 1 = 0x20  or, in unpacked BCD,    100001001 + 1 = 1000000000
0x30 - 1 = 0x29  or, in unpacked BCD,   1100000000 - 1 = 1000001001
0x99 + 1 = 0x100 or, in unpacked BCD, 100100001001 + 1 = 10000000000000000

Z80 Assembly

The DAA function will convert an 8-bit hexadecimal value to BCD after an addition or subtraction is performed. The algorithm used is actually quite complex, but the Z80's dedicated hardware for it makes it all happen in 4 clock cycles, tied with the fastest instructions the CPU can perform.

<lang z80> PrintChar equ &BB5A ;Amstrad CPC kernel's print routine org &1000

ld a,&19 add 1 daa call ShowHex call NewLine

ld a,&30 sub 1 daa call ShowHex call NewLine

ld a,&99 add 1 daa

this rolls over to 00 since DAA only works with the accumulator.
But the carry is set by this operation, so we can work accordingly.

jr nc,continue ;this branch is never taken, it exists to demonstrate the concept of how DAA affects the carry flag. push af ld a,1 call ShowHex pop af continue: call ShowHex call NewLine ret ;return to basic

ShowHex: push af and %11110000 rrca rrca rrca rrca call PrintHexChar pop af and %00001111 ;call PrintHexChar ;execution flows into it naturally. PrintHexChar: ;this little trick converts hexadecimal or BCD to ASCII. or a ;Clear Carry Flag daa add a,&F0 adc a,&40 jp PrintChar</lang>

Output:
20
29
0100