assembler introduction =>
Floating point numbers => Conversion of decimals
(Diese Seite in Deutsch:
Beginner's introduction to AVR assembler language
Floating point arithmetic in assembly language
Converting decimals to binary floating point numbers in assembler language
Following the introduction to binary floating point numbers here
and the conversion of binary floats to decimal format here
we need the opposite of the last: the conversion of decimals to float. And that goes like
Decimal number formats
There are lots of different decimal number formats:
To convert all these formats of decimals to binary floating point numbers, the software
- 1 or 123: decimal fixed integers without decimal dot,
- 12.3: decimal floating point numbers with decimal dots,
- -1.234: negative decimal floating point numbers with decimal dot,
- 1.2345E2, 1.2345E+2, 1.2345E+02: decimal floating point numbers with one or two
decimal exponents, and with or without "+",
- 1.23456E-12: decimal floating point numbers with negative decimal exponents,
- -1.234567E-13: negative decimal floating point numbers with negative decimal
- check whether the decimal is negative (the string starts with "-"),
- convert the unsigned decimal mantissa to a binary number format,
- get the decimal exponent (number of decimal digits before the decimal dot plus the
number Anzahl der Ziffern vor dem Dezimalkomma plus the negative or positive number
following "", if any) and to multiply (positive exponent) or divide (negative
exponent) the binary mantissa, including any changes to the binary exponent,
- normalize the binary mantissa (highest mantissa bit = 1), and to
- invert the mantissa's sign bit and it's content if the input number is negative.
The assembler software for the conversion
The assembler source code here has the decimal number to be
converted to a binary float as a string with ASCII-formatted characters in its flash
memory, together with the conversion code. This string is copied to a location in SRAM
first. This step is necessary to avoid multiple mixed accesses to the flash later on. If
your decimal is already located in the SRAM (e.g. because you received it via serial
communication) you can skip this step. Only ensure that the decimal ends with a null byte
at the end (null-terminated string).
The software is written for binary mantissas of up to 40 bit length and uses an 8-bit
binary exponent. That corresponds to a 48-bit binary float. Those who need less accuracy
can remove the last or the two last bytes and save some execution time with that.
Detecting the negative sign
By default a positive sign is assumed. The pointer X (XH:XL = R27:R26) points to the
beginning of the string and reads the first ASCII character. If that is a minus character,
the flag bMneg is set. The procedure later on uses this flag to format the mantissa
Read the decimal mantissa and convert it to a binary integer
The software then starts reading the decimal mantissa. This is written for english format
(decimal dot), but this is ignored in this stage. The null terminator 0x00 or a
"E" character ends reading the decimal mantissa. Other characters than
ASCII-Zero to ASCII-Nine lead to a jump to the error loop routine. In that case register
R16 holds an ASCII character that characterizes the reason for the failure:
The digits read are, starting with 10,000,000,000 (0x02540BE400), multiplied (by
repeated addition to the result) and added. The next digit reads the next lower decimal
as hex. This is repeated until either the string ends or an "E" ocuurs or the
decimal reaches zero (all other characters following are ignored). To read the decimals
from a table in flash memory avoids to divide the number by 10 and accelerates execution.
- "0": Character smaller than ASCII-zero,
- "9": Character larger than ASCII-nine,
- "E": Exponent larger than +/-39,
- "b": Binary exponent smaller than -128,
- "B": Binary exponent larger than 127.
Calculate the binary mantissa
To convert the binary integer that was read in to a binary mantissa, all mantissa bits
are first cleared. Only the least significant bit in thje mantissa is set, which signals
that all 40 bits have been converted.
Starting with the decimal 1,000,000,000,000 (0xE8D4A51000) this decimal is repeatedly
divided by two. The integer is then compared with this divided number. If the integer
is equal or larger than the divided decimal, the decimal is subtracted and a one is
shifted into the result registers. If not, a zero is shifted into the result registers.
If, after shifting, a zero is shifted out the division by two and the comparison is
repeated. If a one is shifted out, the conversion of 40 bits is complete.
Determine the decimal exponent and convert it
Now the decimal exponent is determined. First the position of the decimal dot is
searched for in the string: any digit left to the dot increases the decimal exponent.
Then the "E" is searched for. If the string ends without this character,
the decimal exponent is already correct. If not, the maximal two decimal digits are
read, converted to a binary byte and this is either added (if the exponent sign is
missing or "+" or is subtracted, if negative (flag bEneg is set).
Now the decimal exponent is checked if it is larger than 40 or smaller than -40. If
so, the error loop is executed.
If the decimal exponent is positive (bit 7 of rDExp is clear), the mantissa
is multiplied by 10. This is performed in a subroutine named Mult10:. To do
this it is first checked if the most significant byte of the mantissa (rR4)
reaches or exceeds 25. If that is the case, the mantissa is shifted right and the
binary exponent is increased. The mantissa is then copied, rotated to the left
two times, then the copy is added and another shift left is performed. This
multiplication is repeated as often as the decimal exponent says.
If the decimal exponent is negative, the mantissa is divided by 10, as often as the
decimal exponent says. Division by 10 can be done in two ways, both are included
in the source code following the label Div10:. Just change the respective
switch either to zero or one. The first type of division by 10 is to shift out the
40 bits one by one and to subtract 10 from the shifted-out bits. If no carry occurs,
a one is shifted into the result registers. If a carry occurred, the subtraction of
the 10 is undone and a zero is shifted in.
The second version of dividing by 10 needs a little bit more source lines, but performs
faster. The accelerated mode copies the previous mantissa, adds five to the mantissa,
then the copy is repeatedly divided by two. The first, the second and the third divided
copies are subtracted from the uprounded mantissa, the fourth and fifth are not
subtracted. Then the following two divided copies are subtracted and the next following
two are not subtracted. The dividing and subtracting ends when the divided copy is
To switch to the accelerated div10 version is useful if many divisions by 10 have to be
performed (in case of a negative decimal exponent). In case of 1E-30 the classical
div10 method needs 24.55 ms, the accelerated method only 14 ms, and so is
nearly double as fast.
The accelerated method is further described here for
divisions by N and here for 10 only.
Normalization and sign processing
Finally the binary mantissa is normalized. It is either shifted to the right (if bit
39 is set) or is shifted left as long as bit 38 of the mantissa is not one. Of course
shifting decreases or increases the binary exponent accordingly.
Those who need normalization with an extra mantissa bit, shift the mantissa left until
bit 39 is one and then clear bit 39. This shifts the most significant bit out and
adds one bit to the mantissa.
Finally: if the flag rDneg is set, the complete mantissa, including the sign bit
39, is inverted.
After all these operations the result binary mantissa is in rR4:rR3:rR2:rR1:rR0,
the binary exponent in rBExp and all is completed.
The table to the right shows results of such conversions from decimal to binary for
selected cases. Displayes is the decimal number, its binary mantissa and exponent,
the result of the conversion of the back to decimal format, as well as the execution
times needed. In all cases the accelerated DIV10 method has been switched on.
As can be seen, the re-converted numbers differ in the fifth or sixth decimal digit.
So is 0.12345651 incorrect for the second "5" in the seventh digit, which
would round up falsely to the sixth digit. This is as expected because with 40 bits
LOG2(40) is little more than 5. If you need it more accurate, use a 56 or 64 bit
mantissa instead. The method is the same, the extensions are pretty small.
Those who want to send the controller into deep bit shifting and away from the relevant
things that also can happen in the controller's life, use floating point conversion.
Conversion of a decimal to a 40-bit float can replace delay routines of around
10 ms. If you need maximum delay, then let your controller convert 1E-36 and
switch the accelerated method off.
Have much fun with playing with this software.
To the page top
©2021 by http://www.avr-asm-tutorial.net