;------------------------------------------- ;Fast binary to decimal conversion (0..999) ; ;Input: NumH:NumL ;Output Hund:Tens_Ones (packed BCD) ; ; ; ;Size: 56 instructions ;Execution time (with return): 57 ; ;8-July-2000 by Nikolai Golovchenko ;23-Aug-2001 ;Based on 8bit BIN2BCD of Scott Dattalo ;------------------------------------------- bin2dec999fast swapf NumL, w ;Add the upper and lower nibbles addwf NumL, w ;to get the one's digit andlw 0x0F skpndc ;Go through a binary to bcd addlw 0x16 ;conversion for just the one's skpndc ;digit addlw 0x06 addlw 0x06 skpdc addlw -0x06 btfsc NumL, 4 ;bit 4 is a special case addlw 0x16 - 1 + 0x6 skpdc addlw -0x06 ;now adjust the ten's digit btfsc NumL, 5 ;2^5 = 32, so add 3 to the ten's addlw 0x30 ;digit if bit 5 is set btfsc NumL, 6 ;2^6 = 64, so add 6 addlw 0x60 ;if bit 6 is set btfsc NumL, 7 ;2^7 = 128, so add 2 (the ten's addlw 0x20 ;digit) if bit 7 is set addlw 0x60 ;convert the ten's digit to bcd clrf Hund rlf Hund, f ;if there's a carry, then the input btfss Hund, 0 ;was greater than 99. addlw -0x60 movwf Tens_Ones movlw 0x66 ;2^8 = 256, so add 0x56 to Tens_Ones btfsc NumH, 0 movlw 0x56 + 0x66 ;add 0x66 for decimal adjust addwf Tens_Ones, f skpnc incf Hund, f clrw skpc iorlw 0x60 skpdc iorlw 0x06 subwf Tens_Ones, f movlw 0x66 ;2^9 = 512, so add 0x12 to Tens_Ones btfsc NumH, 1 movlw 0x12 + 0x66 addwf Tens_Ones, f skpnc incf Hund, f clrw skpc iorlw 0x60 skpdc iorlw 0x06 subwf Tens_Ones, f ; btfsc NumL, 7 ;finish with hundreds ; incf Hund, f ; movlw 2 ; btfsc NumH, 0 ; addwf Hund, f ; movlw 5 ; btfsc NumH, 1 ; addwf Hund, f
; Aug 23, 2001 optimization:
rlf NumL, w ; copy bit 7 to carry
rlf NumH, w ; load w with bits 9 to 7
btfsc NumH, 1 ; add b[9]
addlw 1 ;
addwf Hund, f ; add it to hundreds return
The algorithm is based on the idea of
adding up decimal weights of each
bit. Each bit has a weight (equal to 2^b, b - bit number, 0 to 15), which
can
be represented and added in decimal:
bit
weight hundreds tens
ones
------- ------- ------- ------
-------
0
1
0 0
1
1
2
0 0
2
2
4
0 0
4
3
8
0 0
8
4
16
0 1
6
5
32
0 3
2
6
64
0 6
4
7
128
1 2
8
8
256
2 5
6
9
512
5 1
2
So, if a bit is set, we add the appropriate
decimal digits to our
result. But testing each bit would be very inefficient. There are
however ways to optimize this. You can notice a regularity, for
example, in the ones column. Values almost repeat the binary
weights
(1,2,4,8,6,2,4,8,6,2). That means that we can separate the binary word
in repeatable strings and add these strings together, which would be
the same if we tested each bit.
First, we need to modify the table to
see how to separate the bits in
strings:
bit
weight hundreds tens
ones
------- ------- ------- ------
-------
0
1
0 0
1
1
2
0 0
2
2
4
0 0
4
3
8
0 0
8
4
16
0 1
1+5
5
32
0 2+1
2
6
64
0 4+2
4
7
128
1 2
8
8
256
2 5
1+5
9
512
4+1 1 2
So, the calculations would be:
ones = b[3:0] + b[7:4] + b[9:8] + 5*(b[4]
+ b[8])
tens = b[6:4] + b[6:5] + 2*b[7] + 5*b[8] + b[9]
hund = b[9:7] + b[9]
However, first two sums may exceed the
0 to 9 range, so we would have
to check for that and propagate the overflow to tens and hundreds.
Let's see what the code does...
; add b[3:0] + b[7:4] for
ones
swapf NumL, w ;Add
the upper and lower
nibbles
addwf NumL, w ;to
get the one's digit
andlw 0x0F
; if sum is bigger than 15 (DC is set), add 16 to BCD
result
skpndc ;Go through
a binary to bcd
addlw
0x16 ;conversion for just the one's
; if lower digit overflowed again, add 16
again
skpndc
;digit
addlw 0x06
; make sure the lower digit is in 0..9 range. If not, do BCD
; correction (add 6)
addlw
0x06
skpdc
addlw -0x06
; add now 5*b[4] to ones accumulator and b[4] to tens, doing the
; BCD correction at the same
time
btfsc NumL, 4 ;bit
4 is a special case
addlw 0x16 - 1 +
0x6
skpdc
addlw -0x06
; here we have converted already bits
0 to 4:
; ones = b[3:0] + b[7:4] + b[9:8] + 5*b[4] +
5*b[8]
;
---------------
-------
; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] +
b[9]
;
----
; hund = b[9:7] + b[9]
; now just test bits 5 to 7 and add their decimal weights to
; tens only (bit 7 also affects hundreds). We can add maximum
; 11 to tens digit here, so it will be less than 16 and not
overflow.
;now adjust the ten's
digit
btfsc NumL, 5 ;2^5
= 32, so add 3 to the
ten's
addlw
0x30 ;digit if bit 5 is set
btfsc NumL, 6 ;2^6 = 64, so add
6
addlw
0x60 ;if bit 6 is set
btfsc NumL, 7 ;2^7 = 128, so add 2 (the
ten's
addlw
0x20 ;digit) if bit 7 is set
; do decimal correction for tens and
propagate carry to
hundreds
addlw
0x60 ;convert the ten's digit to
bcd
clrf
Hund
rlf Hund,
f ;if there's a carry, then the
input
btfss Hund, 0 ;was
greater than 99.
addlw
-0x60
movwf Tens_Ones
; Here we are done with bits 5 to 7
(except bit 7 for hundreds):
; ones = b[3:0] + b[7:4] + b[9:8] + 5*b[4] +
5*b[8]
;
---------------
-------
; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] +
b[9]
; --------
---- ------ ------
; hund = b[9:7] + b[9]
; test bit 8, and add what it contributes to ones and
tens
movlw
0x66 ;2^8 = 256, so add 0x56 to
Tens_Ones
btfsc NumH,
0
movlw 0x56 + 0x66 ;add 0x66
for decimal adjust
addwf Tens_Ones, f
; do decimal correction
skpnc
incf Hund,
f
clrw
skpc
iorlw
0x60
skpdc
iorlw
0x06
subwf Tens_Ones, f
; Here we are done with bit 8:
; ones = b[3:0] + b[7:4] + 2*b[9] + b[8] + 5*b[4] +
5*b[8]
;
---------------
----- ------- ------
; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] +
b[9]
; --------
---- ------ ------ ------
; hund = b[9:7] + b[9]
; test bit 9 (it adds 2 to ones, 1 to
tens, and 5 to hundreds),
; but ignore hundreds yet
movlw
0x66 ;2^9 = 512, so add 0x12 to
Tens_Ones
btfsc NumH,
1
movlw 0x12 +
0x66
addwf Tens_Ones,
f
skpnc
incf Hund,
f
clrw
skpc
iorlw
0x60
skpdc
iorlw
0x06
subwf Tens_Ones, f
; Now we have a ready result for ones
and tens:
; ones = b[3:0] + b[7:4] + 2*b[9] + b[8] + 5*b[4] +
5*b[8]
; ---------------
------ ----- ------- ------
; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] +
b[9]
; --------
---- ------ ------ ------ -----
; hund = b[9:7] + b[9]
; now just test bits 7, 8, and 9 and
add their share to hundreds
; (without any BCD correction, because we assume input value is
; between 0 and 999, so hundreds can't be more than 9)
btfsc NumL, 7 ;finish with
hundreds
incf Hund,
f
movlw
2
btfsc NumH,
0
addwf Hund,
f
movlw
5
btfsc NumH,
1
addwf Hund, f
That's
it!
Wait, let's optimize the last few lines...
; hund = b[9:7] + b[9]
rlf NumL,
w
; copy bit 7 to carry
rlf NumH,
w
; load w with bits 9 to 7
btfsc NumH,
1 ; add
b[9]
addlw
1
;
addwf Hund,
f ; add it to
hundreds
Questions:
I have not analyzed the code at http://www.piclist.com/techref/microchip/math/radix/b2a-16b3a2-ng.htm, but there seems to be an error somewhere. It works for numbers 0..299, but 300 comes up as "49:". From here on up, there are various points where it works as expected and others where it doesn't. Anyone have a fix?
...and later comments:
I put your code back and still have a problem. I used the code you sent, which I compared with that on the website. They appear to be identical except that which you sent has more comments (or perhaps it's just that they are in the code instead of at the bottom).Now, here's something I didn't mention before: I'm running this on an 18c452. I've never had problems with instruction changes before, but appear to be having the problem now. I don't fully understand your code, but looking through it a bit, I see where the comments say "test bit 8," and a little below that is "do decimal correction." The decimal correction includes "incf Hund, f". On the 16 series chips, incf and decf affect only Z while on the 18C they affects C, DC, Z, OC, N. A couple lines down is a skpc and a couple more lines down is a skpdc. These are probably relying on status bits set in the addwf Tens_Ones,f a few lines up, but are, instead getting the status caused by the incf.
I've added some conditional assembly to save away the status and restore it where I've seen the problems. The code now seems to work fine. My modified code is below.
Finally THANKS for contributing your code and providing great support! I hope I can return the favor some day!
Code:
;Fast binary to decimal conversion (0..999) ; ;Input: NumH:NumL ;Output Hund:Tens_Ones (packed BCD) ; ; ; ;Size: 56 instructions ;Execution time (with return): 57 ; ;8-July-2000 by Nikolai Golovchenko ;23-Aug-2001 ;Based on 8bit BIN2BCD of Scott Dattalo ;------------------------------------------- cblock NumH NumL Hund Tens_Ones endc ifdef __18C452 cblock TempStatus ; Deal with instruction set changes from 16c to 18c endc endif bin2dec999fast ; convert ; add b[3:0] + b[7:4] for ones swapf NumL, w ;Add the upper and lower nibbles addwf NumL, w ;to get the one's digit andlw 0x0F ; if sum is bigger than 15 (DC is set), add 16 to BCD result skpndc ;Go through a binary to bcd addlw 0x16 ;conversion for just the one's ; if lower digit overflowed again, add 16 again skpndc ;digit addlw 0x06 ; make sure the lower digit is in 0..9 range. If not, do BCD ; correction (add 6) addlw 0x06 skpdc addlw -0x06 ; add now 5*b[4] to ones accumulator and b[4] to tens, doing the ; BCD correction at the same time btfsc NumL, 4 ;bit 4 is a special case addlw 0x16 - 1 + 0x6 skpdc addlw -0x06 ; here we have converted already bits 0 to 4: ; ones = b[3:0] + b[7:4] + b[9:8] + 5*b[4] + 5*b[8] ; --------------- ------- ; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] + b[9] ; ---- ; hund = b[9:7] + b[9] ; now just test bits 5 to 7 and add their decimal weights to ; tens only (bit 7 also affects hundreds). We can add maximum ; 11 to tens digit here, so it will be less than 16 and not overflow. ;now adjust the ten's digit btfsc NumL, 5 ;2^5 = 32, so add 3 to the ten's addlw 0x30 ;digit if bit 5 is set btfsc NumL, 6 ;2^6 = 64, so add 6 addlw 0x60 ;if bit 6 is set btfsc NumL, 7 ;2^7 = 128, so add 2 (the ten's addlw 0x20 ;digit) if bit 7 is set ; do decimal correction for tens and propagate carry to hundreds addlw 0x60 ;convert the ten's digit to bcd clrf Hund rlf Hund, f ;if there's a carry, then the input btfss Hund, 0 ;was greater than 99. addlw -0x60 movwf Tens_Ones ; Here we are done with bits 5 to 7 (except bit 7 for hundreds): ; ones = b[3:0] + b[7:4] + b[9:8] + 5*b[4] + 5*b[8] ; --------------- ------- ; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] + b[9] ; -------- ---- ------ ------ ; hund = b[9:7] + b[9] ; test bit 8, and add what it contributes to ones and tens movlw 0x66 ;2^8 = 256, so add 0x56 to Tens_Ones btfsc NumH, 0 movlw 0x56 + 0x66 ;add 0x66 for decimal adjust addwf Tens_Ones, f ; do decimal correction ifdef __18C452 movff status, tempstatus ; Save status so incf doesn't mess up C and DC endif skpnc incf Hund, f ifdef __18C452 movff tempstatus,status ; Restore the status endif clrw skpc iorlw 0x60 skpdc iorlw 0x06 subwf Tens_Ones, f ; Here we are done with bit 8: ; ones = b[3:0] + b[7:4] + 2*b[9] + b[8] + 5*b[4] + 5*b[8] ; --------------- ----- ------- ------ ; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] + b[9] ; -------- ---- ------ ------ ------ ; hund = b[9:7] + b[9] ; test bit 9 (it adds 2 to ones, 1 to tens, and 5 to hundreds), ; but ignore hundreds yet movlw 0x66 ;2^9 = 512, so add 0x12 to Tens_Ones btfsc NumH, 1 movlw 0x12 + 0x66 addwf Tens_Ones, f ifdef __18C452 movff status, tempstatus ; Save so incf doesn't mess up C and DC endif skpnc incf Hund, f ifdef __18C452 movff tempstatus,status ; Restore saved status endif clrw skpc iorlw 0x60 skpdc iorlw 0x06 subwf Tens_Ones, f ; Now we have a ready result for ones and tens: ; ones = b[3:0] + b[7:4] + 2*b[9] + b[8] + 5*b[4] + 5*b[8] ; --------------- ------ ----- ------- ------ ; tens = 2*b[6:5] + b[4] + b[6:5] + 2*b[7] + 5*b[8] + b[9] ; -------- ---- ------ ------ ------ ----- ; hund = b[9:7] + b[9] ; now just test bits 7, 8, and 9 and add their share to hundreds ; (without any BCD correction, because we assume input value is ; between 0 and 999, so hundreds can't be more than 9) ; btfsc NumL, 7 ;finish with hundreds ; incf Hund, f ; movlw 2 ; btfsc NumH, 0 ; addwf Hund, f ; movlw 5 ; btfsc NumH, 1 ; addwf Hund, f ;That's it! ;Wait, let's optimize the last few lines... ; hund = b[9:7] + b[9] rlf NumL, w ; copy bit 7 to carry rlf NumH, w ; load w with bits 9 to 7 btfsc NumH, 1 ; add b[9] addlw 1 ; addwf Hund, f ; add it to hundreds return
: