The decimal resolution of such a binary number is 4 1/2 digits. To convert these back to decimal we need some more space as each digit needs one byte. So we better place the decimal result, together with some interim numbers that are needed during conversion to the SRAM, so we do not need to mess around with register needs and shortages. You can also increase the resolution simply by extending the SRAM reservation, and the software adds more steps.

15 bits mantissa in binary format corresponds with five decimal digits (2

We need the mantissa's sign bit (if positive we use a blank), the normalized first digit, then the decimal dot, four significant and one insignificant digits, then the

So this is the space that should be reserved for the result. To reserve space for that in assembler, we write:

```
.dseg
.org SRAM_START
DecAsc:
.byte 12 ; Reserve 12 bytes for result
```

The conversion involves adding decimal numbers with
- a resolution of six digits, so we can handle more precision than needed,
- an additional space for three further digits, with which we can do the rounding at the end,
- to the left we add another digit to allow for overflows when multiplying the decimal by two during conversion of the binary exponent.

```
.dseg
.org SRAM_START
.equ MantLength = 10
sMant:
.byte MantLength
sMantEnd:
.byte 16-MantLength
sAdder:
.byte MantLength
sAdderEnd:
.byte 16-MantLength
sDecAsc:
.byte 12
```

The two A basic decision is to handle the calculations in simple binary format, where 0 to 9 are handled as binaries 0 to 9. This requires one byte per digit and does not involve the H flag (in case of packed BCD) or the ASCII format bits when handling ASCII numbers. This is much simpler than in other formats, but needs slightly more time.

In the first step we init the stackpointer, because we use subroutines.

The second step is to get rid of the mantissa's sign bit. If bit 7 of the mantissa is zero we can skip the following. If it is one we subract the LSB from zero and invert the MSB. This makes a positive number from the negative. The decimal mantissa, and also the adder space, with its eight bytes each now look like this.

```
; Initiate the decimal mantissa
InitMant:
ldi ZH,High(sMant) ; Point Z to mantissa space, MSB
ldi ZL,Low(sMant) ; dto., LSB
clr rmp ; Clear the complete mantissa space
ldi rCnt,MantLength
InitMant1:
std Z+dAddMant,rmp ; Clear the adder
st Z+,rmp ; Clear the mantissa
dec rCnt ; At the end?
brne InitMant1
ldi rmp,5 ; Start with bit 15
sts sMant+2,rmp ; Set start value, Mantissa
sts sAdder+2,rmp ; and adder
ret
```

Note that both buffers are filled simultanously with the use of the The init process has been executed in the simulator avr_sim. Both numbers are set to 0.5 now.

The next step is to divide the decimal adder by two to get the adder for bit 13. Procedure starts with the 5 in the buffer and proceeds over the whole buffer length. If the division by two leaves a remainder (as is already the case with the first digit 5 / 2 = 2, remainder = 1), 10 has to be added to the next digit. The division by two is a simple task, as the whole algorithm goes like this:

```
; Divide the adder by two
DivideBy2:
ldi ZH,High(sAdder+1) ; Point to end of the adder, MSB
ldi ZL,Low(sAdder+1)
clc ; Clear carry for overflows
ldi rCnt,MantLength-2 ; Mantissa length minus one to counter
DivideBy2a:
ld rmp,Z ; Read byte from adder
brcc DivideBy2b ; Carry is not set, don't add 10
subi rmp,-10 ; Add ten
DivideBy2b:
lsr rmp ; Divide by two
st Z+,rmp ; Store division result
dec rCnt ; Count down
brne DivideBy2a
ld rmp,Z ; Read last byte from adder
lsr rmp ; Divide by 2
st Z,rmp
brcc Divideby2e
inc rmp ; Round last digit up
Divideby2c:
st Z,rmp ; Correct last digit
subi rmp,10 ; Digit > 10?
brcs DivideBy2e ; Nope
DivideBy2d:
st Z,rmp ; Correct last digit
ld rmp,-Z ; Read pre-last digit
inc rmp ; Increase digit
st Z,rmp ; and store
subi rmp,10 ; Check digit >= 10
brcc DivideBy2d ; Yes, repeat
DivideBy2e:
ret
```

These show the simulation of the first two divisions of the adder.

The special here is to increase the last digit if the division of the last digit shifted a one out to carry. In that case the last digit (as accessible with ld R16,-Z) has to be increased. If that yields equals or more than ten (

To the upper right is the next step seen: if the respective mantissa bit is one, the divided adder has to be added to the decimal mantissa. When adding, the procedure starts at the end of the mantissa and adder buffer and proceeds to the left of the buffer. Each digit has to be checked if, by adding the adder byte and the carry, the 10 has been reached or exceeded. If so, ten has to be subtracted and this overflow has to be added to the next digit.

If the mantissa bit is not one, then the next division takes place without adding. The source code for this is here:

```
; Add the adder to the decimal mantissa
MantAdd:
ldi ZH,High(sMantEnd) ; Point Z to decimal mantissa, MSB
ldi ZL,Low(sMantEnd) ; dto., LSB
ldi rCnt,MantLength-1 ; Mantissa length to R16
clc ; Start with carry clear
MantAdd1:
ld rmp,-Z ; Read last mantissa byte
ldd rmp2,Z+dAddMant ; Read corresponding adder byte
adc rmp,rmp2 ; Add both with carry
st Z,rmp ; Store in SRAM
subi rmp,10 ; Subtract 10
brcs MantAdd2 ; Carry set, smaller than 10
st Z,rmp ; Overwrite digit
sec ; Set carry for next digit
rjmp MantAdd3 ; Count down
MantAdd2:
clc ; Clear carry for next adding
MantAdd3:
dec rCnt ; Count down
brne MantAdd1 ; Not yet complete
ret
```

This shows the treatment of all 15 bits of the mantissa: dividing in any case, and adding only if the mantissa bit is one. Here shown for a mantissa of 0x5555, where every second bit is set one.

- the mantissa, as derived previously,
- the decimal exponent, that starts with zero and increases or decreases when applying the binary exponent, and
- the binary exponent that is to be applied to the mantissa, that can be between -128 and +127, in our example it is four.

- If the overflow-digit in position 0 is not zero, it shifts the complete number once to the right.
- If the first decimal digit in position 1 is zero, it shifts the complete number one or more positions to the left.

This shows the first normalization: a shift to the left. Note that the decimal exponent has now become negative (bit 7 is one).

The source code for normalization:

```
; Normalize the decimal mantissa
Normalize:
lds rmp,sMant ; Read mantissa overflow byte
tst rmp ; Not zero?
brne NormalizeRight ; Shift to the right
Normalize1:
lds rmp,sMant+1 ; Read the first digit
tst rmp ; Zero?
breq NormalizeLeft ; If yes, shift left
ret ; No normalization necessary
; Shift exponent one position left
NormalizeLeft:
ldi ZH,High(sMant+1) ; Point to first digit, MSB
ldi ZL,Low(sMant+1) ; dto., LSB
ldi rCnt,MantLength-2 ; Shift counter
NormalizeLeft1:
ldd rmp,Z+1 ; Read the next byte
st Z+,rmp ; Copy it to the current position
dec rCnt ; Count down
brne NormalizeLeft1 ; Additional bytes to move
clr rmp ; Clear the last digit
st Z,rmp ; in the last buffer
dec rDecExp ; Decrease decimal exponent
rjmp Normalize1 ; Check if further shifts necessary
```

```
; Shift number to the right
NormalizeRight:
ldi ZH,High(sMantEnd-1) ; Place Z to the end, MSB
ldi ZL,Low(sMantEnd-1) ; dto., LSB
ldi rCnt,MantLength-1 ; Counter for digits
NormalizeRight1:
ld rmp,-Z ; Read digit left
std Z+1,rmp ; Store one position to the right
dec rCnt ; Count down
brne NormalizeRight1 ; Furchter digits
clr rmp ; Clear the first digit (overflow digit)
st Z,rmp
inc rDecExp ; Increase decimal exponent
ret
```

The decimal mantissa has been shifted one position to the left and is now normalized.
As the binary exponent is four, now the mantissa has to be multiplied by two. This drecreases the binary exponent by one.

The source code for multiplication by 2 is the following:

```
; Multiply number by 2
Multiply2:
ldi ZH,High(sMantEnd) ; Z to end of mantissa, MSB
ldi ZL,Low(sMantEnd) ; dto., LSB
ldi rCnt,MantLength ; Over the complete length
clc ; No carry on start
Multiply2a:
ld rmp,-Z ; Read last digit
rol rmp ; Multiply by 2 and add carry
st Z,rmp ; Overwrite last digit
subi rmp,10 ; Subtract 10
brcs Multiply2b ; Carry set, smaller than 10
st Z,rmp ; Overwrite last digit
sec ; Set carry for next higher digit
rjmp Multiply2c ; To count down
Multiply2b:
clc ; Clear carry for next higher digit
Multiply2c:
dec rCnt ; Count down
brne Multiply2a ; Further digits to process
ret
```

This is the simulated multiplication by 2. Note that the overflow byte is at one now, so
following each multiplication a check whether another normalization has to be performed.
If so, a right-shift is performed to normalize the decimal mantissa again.
The same happens if the binary exponent is negative (bit 7 = one). In that case the mantissa has to divided by two and the normaization check should repair any losses of the first digit, by shifting the mantissa one or more positions to the left.

These steps are repeated until the binary exponent reaches zero.

```
; Round the mantissa up
RoundUp:
ldi ZH,High(sMantEnd)
ldi ZL,Low(sMantEnd)
ldi rmp2,5
ldi rCnt,3
clc
RoundUp1:
ld rmp,-Z
adc rmp,rmp2
st Z,rmp
subi rmp,10
brcs RoundUp2
st Z,rmp
sec
rjmp RoundUp3
RoundUp2:
clc
RoundUp3:
dec rCnt
brne RoundUp1
ldi rmp2,0
ldi rCnt,MantLength-3
RoundUp4:
ld rmp,-Z
adc rmp,rmp2
st Z,rmp
subi rmp,10
brcs RoundUpRet
dec rCnt
brne RoundUp4
rcall Normalize
RoundUpRet:
ret
```

Note that, under rare circumstances, rounding can lead to an overflow even to the byte 0.
Therefore we finally have to check if an additional normalization is necessary. This is
not the case if the up-rounding chain ends already in a lower byte position.
With that we have our float now complete for its conversion to ASCII format.

- the sign of the decimal mantissa, if it is negative (a blank if otherwise),
- the decimal dot,
- if the decimal exponent is not zero, we'll have to add
**E**, the sign of the decimal exponent, and the exponent in two-digit format. If not we add four blanks.

The complete procedure needs roughly the following times:

Mantissa | Exponent | Duration |
---|---|---|

0x4000 | 0x00 | 448 µs |

0x01 | 668 µs | |

0x02 | 816 µs | |

0x10 | 2.15 ms | |

0x7F | 23.2 ms | |

0xFFFF | 0x00 | 449 µs |

0x5555 | 2.88 ms | |

0x7FFF | 3.87 ms |

The cases with negative mantissas or exponents are not differing much from the postive cases as there are only two additional instructions (a NEG and a COM).

If you need the assembler source code (419 lines) for own experiments or extensions to 32/40/48/56/64 bit floats, here is it: float_conv16.asm".

We do that in the following way:

- It first converts the 32-bit mantissa to an integer value. Because a 32 bit binary can hold decimal numbers of up to 4 billion and hence with 10 digits accuracy, we need an integer that can hold up to five bytes, but as we will have some overflow during multiplications, we use six bytes. For each of the 32 mantissa bits, the decimal representation of the weight of this bit is added to the result. Again, like demonstrated above, we start with 0.5, which is decimal 50.000.000.000 or hexadecimal 0B,A4,3B,74,00. These five bytes are repeatedly divided by two to get the next bit's weight factor as decimal. If the mantissa bit is one, the decimal is added to the result in rAdd5:4:3:2:1:0.
- The integer is then multiplied with the exponent: each positive exponent multiplies the integer by two. If the left-shift shifts a one to byte 6 in rAdd5, the number is divided by 10 and the decimal exponent is increased by one. If the exponent is negative, the number in rAdd5:4:3:2:1:0 is divided by two. If rAdd4 gets empty by shifting, the number is multiplied by 10 (*4 by two left shifts, *5 plus original value, *10 by an additional left shift and the decimal exponent is decreased by one.
- If follows normalization of the decimal integer: If byte 6 in rAdd5 is not zero, the number is divided by 10 and the decimal exponent is increased. If the number in rAdd4:3:2:1:0 is larger than or equal to 1.000.000.000.000 or 0xE8.D4.A5.10.00 (the maximum integer that the following integer-to-decimal conversion can handle), the number is also divided by 10 and the decimal exponent is increased. In case that the number did not exceed the maximum, it is checked whether it is smaller than 100.000.000.000 or 0x17.48.76.E8.00. If that is the case, the number is multiplied by 10 and the decimal exponent is decreased. That ensures that the first digit is at least a one (normalization of the decimal).
- The integer is then converted to a decimal value. This is done by subtracting 100.000.000.000 repeatedly from the integer until an underflow occurs. This leads to the first digit and the decimal subtractor is added again. The decimal dot then follows. The following digits are derived by repeatedly subtracting the next lower decade, and down until 10. The last digit is the rest of the number.

The table on the right shows the results for various input combinations.

The source code in assembler format can be downloaded from here. If you like to use it for serious applications: add another byte to the right to get increased accuracy and reduce the Div10 routine from 48 down to 40 bits in cases where no overflow is in rAdd5 to increase the execution speed.

To the page top

©2021 by http://www.avr-asm-tutorial.net