0

I have a binary number, stored in an array of bytes (unsigned chars), and want to turn it into a decimal string.

The "obvious" approach that i found online was, to iterate over the array, add everything up while keeping track of the base and then converting it into a string but this doesn't work for me because the whole number doesn't fit into any common datatype and therefor cannot be added up in one go.

typedef unsigned char byte;

typedef struct Bin {
    int size;
    byte *ptrToVal;
} Bin;

void asDecString(Bin* this) {
    signed int n = 0;
    for(int i = 0; i < this->size; i++) {
        n += this->ptrToVal[i] << (i * 8);
        printf("%u\t%u\t%u\n", this->ptrToVal[i], n, i);
    }
    printf("%u\n", n);
}

The second, slow approach is to store the number in a string and multiply the digits in the string.

I'm looking for a quick way to implement this in c, but because I'm completely new to the language I don't know the features that well.

4
  • 1
    You may want to take a look at the source code of the GNU Multiple Precision Arithmetic Library. It probably does exactly what you are looking for, or at least something similar. Commented Jul 24, 2022 at 0:26
  • Have a look at this [link]my.eng.utah.edu/~nmcdonal/Tutorials/BCDTutorial/… It describes one byte, but you could be shifting LSBs into that byte until you exhaust your source array... Commented Jul 24, 2022 at 2:33
  • 1
    As a side note, there's the standard type uint8_t, you might rather use that than your own byte typedef. Commented Jul 25, 2022 at 11:10
  • As a general comment on the subject, printing out a binary bignum as decimal number is same as converting the bignum to a bignum with one-decimal-digit-per-byte representation. And any bit in the binary bignum may change any decimal digit in the decimal bignum. There's really no "fast shortcut" for the general case. Commented Jul 25, 2022 at 11:21

2 Answers 2

1

Out of curiosity and interest, here's my implementation of the algorithm found at: https://my.eng.utah.edu/~nmcdonal/Tutorials/BCDTutorial/BCDConversion.html

It outputs intermediary values as each bit is processed. The verbose block can be moved out of the loop after testing.

Try it with one or two 'hex' bytes to begin with.

#include <stdio.h>

typedef unsigned char binByte;

void toBCD( binByte *p, int size ) {
    const int decSize = 50; // Arbitrary limit of 10^49.
    binByte decDgt[ decSize ]; // decimal digits as binary 'nibbles'.
    int curPow10 = 0;

    memset( decDgt, 0, sizeof(decDgt) );

    for( int i = 0; i < size; i++ ) {
        binByte oneByte = p[ i ]; // Initial one byte value

        for( binByte bit = 0x80; bit > 0; bit >>= 1 ) {

            for( int ii = 0; ii <= curPow10; ii++ )
                if( decDgt[ ii ] >= 5 ) // Algorithm step
                    decDgt[ ii ] += 3;

            for( ii = curPow10; ii >= 0; ii-- ) {
                decDgt[ ii ] <<= 1;

                if( decDgt[ ii ] & 0x10 ) { // Carry high bit?
                    decDgt[ ii + 1 ] |= 0x1;
                    if( ii == curPow10 ) // new power of 10?
                        curPow10++;
                }
                decDgt[ ii ] &= 0xF; // dealing in 'nibbles'
            }
            decDgt[ 0 ] |= ( (oneByte & bit) != 0 ); // truth value 0 or 1

            printf( "Bottom: " );
            for( ii = curPow10; ii >= 0; ii-- )
                putchar( decDgt[ ii ] + '0' );
            putchar( '\n' );
        }
    }
}

void main( void ) {
    binByte x[] = { 0x01, 0xFF, 0xFF, 0xFF, 0xFF };
    toBCD( x, sizeof(x) );
}
Sign up to request clarification or add additional context in comments.

Comments

0

For large integers; you want to break them into "small enough integers", then convert the "small enough integers" into digits.

For example, lets say you have the number 1234567890. By doing next_piece = number / 100; number = number % 100; you could split it into pieces that fit in one byte, then print the smaller pieces 12, 34, 56, 78, 90 (with nothing between them and leading zeros to ensure the piece 03 doesn't get printed as 3) so that it looks like a single larger number.

In the same way you could split it into pieces that fit in a 32-bit unsigned integer; so that each piece is from 0 to 1000000000, by doing something like next_piece = number / 1000000000; number = number % 1000000000;. For example, the number 11223344556677889900112233445566778899 could be split into 11, 223344556, 677889900, 112233445, 566778899 and then printed (with leading zeros, etc).

Of course for big integers you'd need to implement a "divide by 1000000000" that works with your data structure, that returns a uint32_t (the remainder or the next piece) and the original value divided by 1000000000.

This is where things get messy. Using an array of bytes is slow, and signed numbers are painful. To fix that you'd want something more like:

#include <stdint.h>
typedef struct AlternativeBin {
    int signFlag;
    int size;
    uint32_t *ptrToVal;
} AlternativeBin;

It wouldn't be hard to convert from your original format into this alternative format (if you can't just use this alternative format for everything).

The division loop would look something like (untested):

// WARNING: Destructive (the input value is used to return the quotient)

uint32_t getNextPiece(AlternativeBin * value) {
    uint64_t temp = 0;
    int i;

    // Do the division

    for(i = value->size - 1; i >= 0; i--) {
        temp = (temp << 32) | value->ptrToVal[i];
        value->ptrToVal[i] = temp / 1000000000ULL;
        temp = temp % 1000000000ULL;
    }

    // Reduce the size of the array to improve performance next time

    while( (value->size > 0) && (value->ptrToVal[value->size - 1] == 0) ) {
        value->size--;
    }

    return temp;
}

..which means the "printing loop" (using recursion to reverse the order of pieces) might look like (untested):

#include <stdio.h>
#include <inttypes.h>
// WARNING: Recursive and destructive (the input value is destroyed)

void printPieces(AlternativeBin * value) {
    uint32_t piece;

    piece = getNextPiece(value);
    if( !value_became_zero(value) ) {
        printPieces(value);
        printf("%09" PRIu32, piece);   // Print it with leading zeros
    } else {
        printf("%" PRIu32, piece);     // No leading zeros on first piece printed
    }
}

The other minor inconvenience is that you'll want to print a '-' at the start if the value is negative (untested):

// WARNING: Destructive (the input value is destroyed)

void printValue(AlternativeBin * value) {
    if(value->signFlag) {
        printf("-");
    }
    printPieces(value);
}

If you wanted you could also create a copy of the original data here (get rid of the WARNING: Destructive comment by destroying a copy and leaving the original unmodified); and convert from your original structure (bytes) into the alternative structure (with uint32_t).

Note that it would be possible to do all of this with bytes (something like your original structure) using a divisor of 100 (instead of 1000000000). It'd just be a lot slower because the expensive getNextPiece() function will need to be called about 4.5 as many times.

6 Comments

Thanks for the elaborate answer. Can you explain why "signed numbers are painful"? My implementation was unsigned but yours added a flag.
@xtay2: Your example code looks like it's converting a Bin into a signed integer (signed int n not unsigned int n, printf("%d\n", n); not printf("%u\n", n);); and I suspect your example code would actually work for signed numbers (e.g. assuming signed int is 32 bits, if the bytes were 0xFF, 0xFF, 0xFF, 0xC0 it'd become -64). More specifically; I suspected your Bin was using a 2's complement scheme where the highest bit of the most significant byte in the array is used as a sign flag.
@xtay2: If the highest bit of the most significant byte in the array was used as a sign flag; then it'd complicate everything so much that I'm not even sure how I'd attempt to make it work (without just negating negative numbers). That's where my "it's painful" comes from.
I just didn't know about "%u" as these are my first steps with c. But no, the whole binary number shouldn't be signed at all.
@Fe2O3: It addresses the issue of the value represented by the array not fitting in a standard type by showing clearly that the value represented by the array does not need to fit in a standard type at all.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.