7

I know you can use this table to convert decimal to BCD:

0 0000

1 0001

2 0010

3 0011

4 0100

5 0101

6 0110

7 0111

8 1000

9 1001

Is there a equation for this conversion or you have to just use the table? Im trying to write some code for this conversion but Im not sure how to do the math for it. Suggestions?

Bramble
  • 1,395
  • 13
  • 39
  • 55
  • What kind of conversion are you trying to do? The number `5` is already `0101` without needing any conversion, `7` is `0111` etc. – LukeH Sep 11 '09 at 00:14
  • Yes. The question needs more precision. BCD can mean many things. Is it one decimal digit per byte? Two decimal digits per byte? – dan-gph Sep 11 '09 at 00:22
  • Processors with native BCD support (like the m68k family) often (generally?) use two decimal digits to the byte. – dmckee --- ex-moderator kitten Sep 11 '09 at 00:46
  • you can have packed BCD, two digits in one byte: example: 99 -> 10011001b and unpacked which is one digit in one byte example: 9 -> 00001001b. When you want to write some code for it, keep this in your mind. – Agguro May 26 '17 at 17:51

10 Answers10

8

You know the Binary numeral system, don't you?

Especially have a look at this chapter.

EDIT: Also note KFro's comment that the lower nibble (= 4 bits) of the binary ASCII representation of numerals is in BCD. This makes conversions BCD <-> ASCII very easy as you just have to add/remove the leading 4 bits:

Number    ASCII Code
0         0011 0000
1         0011 0001
 ...
8         0011 1000
9         0011 1001
schnaader
  • 49,103
  • 10
  • 104
  • 136
  • 3
    It should also be noted that ASCII representations for the numerals are also in BCD (on purpose)...well at least the lower nybble. – KFro Sep 11 '09 at 00:57
  • 2
    Er...KFro. That is not terribly clear. Do you mean that the lower nibble of the ASCII representation for the digits coresponds to the BCD representation of the digit? – dmckee --- ex-moderator kitten Sep 11 '09 at 01:11
  • 1
    @dmckee: That's the case, so I assume that's what KFro meant: `"0"` is `0x30`, `"1"` is `0x31` etc. – LukeH Sep 11 '09 at 01:26
  • ASCII's digits are NOT BCD equivalent, but EBCDIC are. IE: the 1st 10 entries in the EBCDIC table are the BCD digits 0-9 where the most significant nibble is 0b0000, whereas in ASCII they are offset hex 30, IE: ASCII '0' == 0x30 in the ASCII table. –  Feb 03 '14 at 00:15
  • 1
    @RocketRoy That's actually not true. EBCDIC entries for digits are in the range `0xf0` to `0xf9`. – fuz Nov 03 '14 at 12:39
  • @FUZxxl, I stand corrected. You're right. 0xf0 to 0xf9 it is! The question is so poorly specified I can't tell what the OP is asking for, but if ASCII to BCD then subtract 0x30 off the char and push the last 4 bits into a buffer until you run out of digits. A nice way to do this is to use '0' instead of 0x30. It's easier to understand what's being done when someone else is reading your code 5 yrs later and you're long gone. This answer isn't very helpful. Code to push two nybbles onto a char and then concatenate chars into a "string" works well and is clean. –  Nov 03 '14 at 23:10
8
#include <stdint.h>

/* Standard iterative function to convert 16-bit integer to BCD */
uint32_t dec2bcd(uint16_t dec) 
{
    uint32_t result = 0;
    int shift = 0;

    while (dec)
    {
        result +=  (dec % 10) << shift;
        dec = dec / 10;
        shift += 4;
    }
    return result;
}

/* Recursive one liner because that's fun */
uint32_t dec2bcd_r(uint16_t dec)
{
    return (dec) ? ((dec2bcd_r( dec / 10 ) << 4) + (dec % 10)) : 0;
}
Simon Peverett
  • 4,128
  • 3
  • 32
  • 37
  • 1
    I like this a lot. Very clean. RX: char * dec2bcd(char *dest, uint16t src) where dec2bcd() copies the nybbles used in result into dest and returns dest like most C Standard Library functions. USHRT_MAX, only requires 5 nybbles, AKA 3 bytes. Here's a faster approach, but I like yours a lot - as far as it goes. http://stackoverflow.com/questions/21501815/optimal-base-10-only-itoa-function –  Nov 04 '14 at 01:19
7

This is from the micro controller world.... Note that values are rounded in the division. For instance 91 to BCD would be 91/10 * 16 = 144 + 91%10 = 145. Converted to Binary is 10010001.

uint8_t bcdToDec(uint8_t val)
{
  return ( (val/16*10) + (val%16) );
}

uint8_t decToBcd(uint8_t val)
{
  return ( (val/10*16) + (val%10) );
}
nmushov
  • 145
  • 1
  • 8
  • 2
    Clever. This would be more clear though if written as return ( ((val/10)<<4) + (val%10)); as it would be clear that multiplying by 16 is actually left-shifting by 4 bits to move the 1st nybble into the most-significant position in the byte. –  Nov 04 '14 at 04:33
5

Usually when someone says they want to convert from decimal to BCD, they're talking about more than one decimal digit.

BCD is often packed into two decimal digits per byte (because 0..9 fit in 4 bits, as you've shown), but I think it's more natural to use an array of bytes, one per decimal digit.

An n-bit unsigned binary number will fit into ceil(n*log_2(10)) = ceil(n/log10(2)) decimal digits. It will also fit in ceil(n/3) = floor((n+2)/3)) decimal digits, since 2^3=8 is less than 10.

With that in mind, here's how I'd get the decimal digits of an unsigned int:

#include <algorithm>
#include <vector>

template <class Uint>
std::vector<unsigned char> bcd(Uint x) {  
  std::vector<unsigned char> ret;
  if (x==0) ret.push_back(0); 
  // skip the above line if you don't mind an empty vector for "0"
  while(x>0) {
    Uint d=x/10;
    ret.push_back(x-(d*10)); // may be faster than x%10
    x=d;
  }
  std::reverse(ret.begin(),ret.end());
  // skip the above line if you don't mind that ret[0] is the least significant digit
  return ret;
}

Of course, if you know the width of your int type, you may prefer fixed length arrays. There's also no reason to reverse at all if you can remember the fact that the 0th digit is the least significant and reverse only on input/output. Keeping the least significant digit as the first simplifies digit-wise arithmetic ops in the case that you don't use a fixed number of digits.

If you want to represent "0" as the single "0" decimal digit rather than the empty digit-string (either is valid), then you'd check specifically for x==0.

Jonathan Graehl
  • 9,182
  • 36
  • 40
  • 1
    BCD using only the trailing nybble in a byte is very inefficient. MySQL abandoned that approach years ago, and more recently, BCD entirely for decimal data. You can easily left-shift the otherwise-wasted leading nybble off a byte while pushing the 2nd digit onto it. Then just concatenate the double-nybble/byte/char onto a char array. Simple, efficient, straight forward. –  Nov 03 '14 at 23:17
2

If you want two decimal digits per byte, and "unsigned" is half the size of "unsigned long" (use uint32 and uint64 typedefs if you want):

unsigned long bcd(unsigned x) {
  unsigned long ret=0;
  while(x>0) {
    unsigned d=x/10;
    ret=(ret<<4)|(x-d*10);
    x=d;
  }
  return ret;
}

This leaves you with the least significant (unit) decimal digit in the least significant half-byte. You can also execute the loop a fixed number (10 for uint32) of times, not stopping early when only 0 bits are left, which would allow the optimizer to unroll it, but that's slower if your numbers are often slow.

Jonathan Graehl
  • 9,182
  • 36
  • 40
0

Would something like this work for your conversion?

#include <string>
#include <bitset>

using namespace std;

string dec_to_bin(unsigned long n)
{
    return bitset<numeric_limits<unsigned long>::digits>(n).to_string<char, char_traits<char>, allocator<char> >();
}
CTT
  • 16,901
  • 6
  • 41
  • 37
0

This code encodes and decodes. Benchmarks are as follows.

  • 45 clocks for the round-trip
  • 11 clocks for unpacking BCD to uint32_t
  • 34 clocks for packing uint32_t into BCD

I used an uint64_t to store the BCD here. Very convenient and fixed width, but not very space efficient for large tables. Pack the BCD digits, 2 to char[] for that.

// -------------------------------------------------------------------------------------
uint64_t uint32_to_bcd(uint32_t usi)    {

    uint64_t shift = 16;  
    uint64_t result = (usi % 10);

    while (usi = (usi/10))  {
        result += (usi % 10) * shift;
        shift *= 16; // weirdly, it's not possible to left shift more than 32 bits
    }
    return result;
}
// ---------------------------------------------------------------------------------------
uint32_t bcd_to_ui32(uint64_t bcd)  {

    uint64_t mask = 0x000f;
    uint64_t pwr = 1;

    uint64_t i = (bcd & mask);
    while (bcd = (bcd >> 4))    {
        pwr *= 10;
        i += (bcd & mask) * pwr;
    }
    return (uint32_t)i;
}
// --------------------------------------------------------------------------------------
const unsigned long LOOP_KNT = 3400000000; // set to clock frequencey of your CPU
// --------------------------------------------------------------------------------------
int main(void)  {
    time_t start = clock();
    uint32_t foo, usi = 1234; //456;
    uint64_t result;
    unsigned long i;

    printf("\nRunning benchmarks for %u loops.", LOOP_KNT);

    start = clock();
    for (uint32_t i = 0; i < LOOP_KNT; i++) {
        foo = bcd_to_ui32(uint32_to_bcd(i >> 10));
    }
    printf("\nET for bcd_to_ui32(uint_16_to_bcd(t)) was %f milliseconds. foo %u", (double)clock() - start, foo);


    printf("\n\nRunning benchmarks for %u loops.", LOOP_KNT);

    start = clock();
    for (uint32_t i = 0; i < LOOP_KNT; i++) {
        foo = bcd_to_ui32(i >> 10);
    }
    printf("\nET for bcd_to_ui32(uint_16_to_bcd(t)) was %f milliseconds. foo %u", (double)clock() - start, foo);

    getchar();
    return 0;
}

NOTE: It appears that it's impossible, even with 64-bit ints, to shift left more than 32 bits, but fortunately, it's entirely possible to multiply by some factor of 16 - which happily has the desired effect. It's also much faster. Go figure.

0

I know this has been previously answered but I've extended this for unsigned ints of different sizes using a template to build the specific code.

#include <stdio.h>
#include <unistd.h>

#include <stdint.h>

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

constexpr int nBCDPartLength = 4;
constexpr int nMaxSleep = 10000; // Wait enough time (in ms) to check out the boundry cases before continuing.

// Convert from an integer to a BCD value.
// some ideas for this code are from :
//  http://stackoverflow.com/questions/1408361/unsigned-integer-to-bcd-conversion
//  &&
//  http://stackoverflow.com/questions/13587502/conversion-from-integer-to-bcd
// Compute the last part of the information and place it into the result location.
// Decrease the original value to place the next lowest digit into proper position for extraction.
template<typename R, typename T> R IntToBCD(T nValue) 
{
    int nSizeRtn = sizeof(R);
    char acResult[nSizeRtn] {};
    R nResult { 0 };
    int nPos { 0 };

    while (nValue)
    {
        if (nPos >= nSizeRtn)
        {
            return 0;
        }

        acResult[nPos] |= nValue % 10;
        nValue /= 10;

        acResult[nPos] |= (nValue % 10) << nBCDPartLength;
        nValue /= 10;

        ++nPos;
    }

    nResult = *(reinterpret_cast<R *>(acResult));

    return nResult;
}

int main(int argc, char **argv)
{
    //uint16_t nValue { 10 };
    //printf("The BCD for %d is %x\n", nValue, IntToBCD<uint32_t, uint16_t>(nValue));

    // UINT8_MAX    =   (255)                               - 2 bytes can be held in uint16_t (2 bytes)
    // UINT16_MAX   =   (65535)                             - 3 bytes can be held in uint32_t (4 bytes)
    // UINT32_MAX   =   (4294967295U)                       - 5 bytes can be held in uint64_t (8 bytes)
    // UINT64_MAX   =   (__UINT64_C(18446744073709551615))  - 10 bytes can be held in uint128_t (16 bytes)


    // Test edge case for uint8
    uint8_t n8Value { UINT8_MAX - 1 };
    printf("The BCD for %u is %x\n", n8Value, IntToBCD<uint16_t, uint8_t>(n8Value));
    // Test edge case for uint16
    uint16_t n16Value { UINT16_MAX - 1 };
    printf("The BCD for %u is %x\n", n16Value, IntToBCD<uint32_t, uint16_t>(n16Value));
    // Test edge case for uint32
    uint32_t n32Value { UINT32_MAX - 1 };
    printf("The BCD for %u is %" PRIx64 "\n", n32Value, IntToBCD<uint64_t, uint32_t>(n32Value));
    // Test edge case for uint64
    uint64_t n64Value { UINT64_MAX - 1 };
    __uint128_t nLargeValue = IntToBCD<__uint128_t, uint64_t>(n64Value);
    uint64_t nTopHalf = uint64_t(nLargeValue >> 64);
    uint64_t nBottomHalf = uint64_t(nLargeValue);
    printf("The BCD for %" PRIu64 " is %" PRIx64 ":%" PRIx64 "\n", n64Value, nTopHalf, nBottomHalf);

    usleep(nMaxSleep);

    // Test all the values
    for (uint8_t nIdx = 0; nIdx < UINT8_MAX; ++nIdx)
    {
        printf("The BCD for %u is %x\n", nIdx, IntToBCD<uint16_t, uint8_t>(nIdx));
    }

    for (uint16_t nIdx = 0; nIdx < UINT16_MAX; ++nIdx)
    {
        printf("The BCD for %u is %x\n", nIdx, IntToBCD<uint32_t, uint16_t>(nIdx));
    }

    for (uint32_t nIdx = 0; nIdx < UINT32_MAX; ++nIdx)
    {
        printf("The BCD for %u is %" PRIx64 "\n", nIdx, IntToBCD<uint64_t, uint32_t>(nIdx));
    }

    for (uint64_t nIdx = 0; nIdx < UINT64_MAX; ++nIdx)
    {
        __uint128_t nLargeValue = IntToBCD<__uint128_t, uint64_t>(nIdx);
        uint64_t nTopHalf = uint64_t(nLargeValue >> 64);
        uint64_t nBottomHalf = uint64_t(nLargeValue);
        printf("The BCD for %" PRIu64 " is %" PRIx64 ":%" PRIx64 "\n", nIdx, nTopHalf, nBottomHalf);
    }
    return 0;
}
Andrew Stern
  • 688
  • 9
  • 18
0

Here is a macro for uint16_t, so that it gets evaluated at compile-time (provided that u is a pre-defined constant). This agrees with dec2bcd() from above up to 9999.

#define U16TOBCD(u) ((((u/1000)%10)<<12)|(((u/100)%10)<<8)|\
                    (((u/10)%10)<<4)|(u%10))
Ray Foulk
  • 1
  • 2
0

Just simplified it.

#include <math.h>
#define uint unsigned int

uint Convert(uint value, const uint base1, const uint base2)
{
    uint result = 0;
    for (int i = 0; value > 0; i++)
    {
        result += value % base1 * pow(base2, i);
        value /= base1;
    }
    return result;
}

uint FromBCD(uint value)
{
    return Convert(value, 16, 10);
}

uint ToBCD(uint value)
{
    return Convert(value, 10, 16);
}
randz
  • 1
  • 1