将纯二进制整数转换为BCD格式

6
我现在太愚笨了,无法解决这个问题...
我得到一个BCD数字(每个数字都有自己的4位表示)
例如,我想要:
输入:202(十六进制)== 514(十进制) 输出:BCD 0x415 输入:0x202 位表示:001000000010 = 514
我尝试了什么:
unsigned int uiValue = 0x202;
unsigned int uiResult = 0;
unsigned int uiMultiplier = 1;
unsigned int uiDigit = 0;


// get the dec bcd value
while ( uiValue > 0 )
{
    uiDigit= uiValue & 0x0F;
    uiValue >>= 4;
    uiResult += uiMultiplier * uiDigit;
    uiMultiplier *= 10;
}

但是我知道这是非常错误的,用比特表示法来表示应该是202,然后分成5个半字节,再重新表示为十进制数

我可以在纸上解决这个问题,但是我就是无法用简单的C代码实现它


1
我可以建议,在处理字节和半字节时,十六进制掩码更容易理解上下文。例如,0x0F比15更明显(至少对我来说是这样!) - Andrew
你的代码看起来很好。十进制数202转换成二进制是1100 1010,或者分成半字节:十进制数64,因此是十进制数64的BCD表示形式。 - Daniel Gehriger
1
@Andrew:但他说的是uiValue = 202,而不是0x202...。如果是0x202,那么BCD值就是十进制的202... - Daniel Gehriger
1
@Sagi:因此,我在下面给出了我的答案... - Daniel Gehriger
@Sagi,针对你的问题我的回答中有一个持续的讨论,讨论的问题是你是否真的想要BCD作为你的输出(与你的问题陈述相符),还是一个常规的int(与你的问题标题和第一次修订的问题一致)。请提供你所需的正确结果的二进制表示形式,输入为0010 0000 0010。 - panda-34
显示剩余5条评论
10个回答

15
您把方向搞反了。您的代码是将 BCD 转换为二进制,就像您问题的(原始)标题所说的那样。但是,如果您要从 二进制转换为 BCD,那么提供的输入和输出值只有在该情况下才正确。在这种情况下,请尝试以下操作:
#include <stdio.h>

int main(void) {

   int binaryInput = 0x202; 
   int bcdResult = 0;
   int shift = 0;

   printf("Binary: 0x%x (dec: %d)\n", binaryInput , binaryInput );

   while (binaryInput > 0) {
      bcdResult |= (binaryInput % 10) << (shift++ << 2);
      binaryInput /= 10;
   }

   printf("BCD: 0x%x (dec: %d)\n", bcdResult , bcdResult );
   return 0;
}

证明:http://ideone.com/R0reQh


谢谢,那就是我需要的 :)它几乎与我拥有的代码相同,但我走错了方向...感谢 - Sagi
抱歉,我的前一条评论有误,但我无法删除它。 - PapaAtHome
非常感谢,这对我很有帮助。小小的提醒:不确定这是否是您的目标,但输出的BCD半字节按“最低有效位优先”排序。在我的情况下,我希望半字节按“最高有效位优先”排序,因此我必须将每个半字节左移4的递增倍数(在取模后进行移位),然后将预先移位的半字节与结果进行OR运算。请注意,算法(%10,/=10)从最低位十进制数字开始,然后继续到更高的位数。 - frr
@frr - 当然,你是正确的。那只是一个简单的错误,我已经修复了代码。感谢你的反馈! - Daniel Gehriger
1
对于那些除法指令成本较高的平台(可以说是所有平台),您可以将 while (binaryInput > 0) 重构为 while (binaryInput > 9),并将二进制输入中小于等于9的任何余数加到最终值中,而无需再次进行除法运算。 - thomasrutter

2
尝试以下操作。
unsigned long toPackedBcd (unsigned int val)
{
  unsigned long bcdresult = 0; char i;


  for (i = 0; val; i++)
  {
    ((char*)&bcdresult)[i / 2] |= i & 1 ? (val % 10) << 4 : (val % 10) & 0xf;
    val /= 10;
  }
  return bcdresult;
}

也可以尝试以下变体(虽然可能有点低效)
/*
Copyright (c) 2016 enthusiasticgeek<enthusiasticgeek@gmail.com> Binary to Packed BCD
This code may be used (including commercial products) without warranties of any kind (use at your own risk)
as long as this copyright notice is retained.
Author, under no circumstances, shall not be responsible for any code crashes or bugs.
Exception to copyright code: 'reverse string function' which is taken from http://stackoverflow.com/questions/19853014/reversing-a-string-in-place-in-c-pointers#19853059
Double Dabble Algorithm for unsigned int explanation

255(binary) - base 10 -> 597(packed BCD) - base 16
     H|    T|    U|        (Keep shifting left)
               11111111
             1 1111111
            11 111111  
           111 11111
          1010 11111    <-----added 3 in unit's place (7+3 = 10) 
        1 0101 1111  
        1 1000 1111     <-----added 3 in unit's place (5+3 = 8)
       11 0001 111
      110 0011 11       
     1001 0011 11       <-----added 3 in ten's place (6+3 = 9)
   1 0010 0111 1  
   1 0010 1010 1        <-----added 3 in unit's place (7+3 = 10)
  10 0101 0101  -> binary 597 but bcd 255
  ^    ^    ^  
  |    |    |
  2    5    5   
*/
#include <stdio.h>   
#include <string.h>

//Function Prototypes
unsigned int binaryToPackedBCD (unsigned int binary); 
char * printPackedBCD(unsigned int bcd, char * bcd_string);

// For the following function see http://stackoverflow.com/questions/19853014/reversing-a-string-in-place-in-c-pointers#19853059
void reverse(char *str);

//Function Definitions
unsigned int binaryToPackedBCD (unsigned int binary) {
  const unsigned int TOTAL_BITS = 32;
  /*Place holder for bcd*/
  unsigned int bcd = 0;
  /*counters*/
  unsigned int i,j = 0;
  for (i=0; i<TOTAL_BITS; i++) {
     /*
      Identify the bit to append  to LSB of 8 byte or 32 bit word -
      First bitwise AND mask with 1. 
      Then shift to appropriate (nth shift) place. 
      Then shift the result back to the lsb position. 
     */
      unsigned int binary_bit_to_lsb = (1<<(TOTAL_BITS-1-i)&binary)>>(TOTAL_BITS-1-i);
      /*shift by 1 place and append bit to lsb*/
      bcd = ( bcd<<1 ) | binary_bit_to_lsb;       
      /*printf("=> %u\n",bcd);*/
      /*Don't add 3 for last bit shift i.e. in this case 32nd bit*/
      if( i >= TOTAL_BITS-1) { 
      break;
      }
      /*else continue*/
      /* Now, check every nibble from LSB to MSB and if greater than or equal 5 - add 3 if so */
      for (j=0; j<TOTAL_BITS; j+=4) {
        unsigned int temp = (bcd & (0xf<<j))>>j;
        if(temp >= 0x5) {
        /*printf("[%u,%u], %u, bcd = %u\n",i,j, temp, bcd);*/
        /*Now, add 3 at the appropriate nibble*/
         bcd = bcd  + (3<<j);
        // printf("Now bcd = %u\n", bcd);
        }
      }
  }
  /*printf("The number is %u\n",bcd);*/
  return bcd;
}   

char * printPackedBCD(unsigned int bcd, char * bcd_string) {
  const unsigned int TOTAL_BITS = 32;
  printf("[LSB] =>\n");
   /* Now, check every nibble from LSB to MSB and convert to char* */
  for (unsigned int j=0; j<TOTAL_BITS; j+=4) {
  //for (unsigned int j=TOTAL_BITS-1; j>=4; j-=4) {
      unsigned int temp = (bcd & (0xf<<j))>>j;
      if(temp==0){
    bcd_string[j/4] = '0';      
      } else if(temp==1){
    bcd_string[j/4] = '1';
      } else if(temp==2){
    bcd_string[j/4] = '2';
      } else if(temp==3){
    bcd_string[j/4] = '3';
      } else if(temp==4){
    bcd_string[j/4] = '4';
      } else if(temp==5){
    bcd_string[j/4] = '5';
      } else if(temp==6){
    bcd_string[j/4] = '6';
      } else if(temp==7){
    bcd_string[j/4] = '7';
      } else if(temp==8){
    bcd_string[j/4] = '8';
      } else if(temp==9){
    bcd_string[j/4] = '9';
      } else {
    bcd_string[j/4] = 'X';
      }
      printf ("[%u - nibble] => %c\n", j/4, bcd_string[j/4]);
  }      
  printf("<= [MSB]\n");
  reverse(bcd_string);
  return bcd_string;
}

// For the following function see http://stackoverflow.com/questions/19853014/reversing-a-string-in-place-in-c-pointers#19853059
void reverse(char *str)
{ 
    if (str != 0 && *str != '\0') // Non-null pointer; non-empty string
    {
    char *end = str + strlen(str) - 1; 
    while (str < end)
    {
        char tmp = *str; 
        *str++ = *end; 
        *end-- = tmp;
    } 
    }
}

int main(int argc, char * argv[])
{
  unsigned int number = 255;
  unsigned int bcd = binaryToPackedBCD(number);
  char bcd_string[8];
  printPackedBCD(bcd, bcd_string);
  printf("Binary (Base 10) = %u => Packed BCD (Base 16) = %u\n OR \nPacked BCD String = %s\n", number, bcd, bcd_string);
  return 0;
}

我测试了这个函数,最初看起来它是有效的,但我还没有尝试过用非常大的数字或与其他候选算法比较其效率来进行例如从“11”转换为“17”的转换。(我的项目是针对Altera DE2 FPGA的7段LED的C代码。) - Niklas Rosencrantz

1

我的两分钱,我需要类似于使用BCD编码的RTC芯片来编码时间和日期信息。我想出了以下宏,满足了要求:

#define MACRO_BCD_TO_HEX(x) ((BYTE) ((((x >> 4) & 0x0F) * 10) + (x & 0x0F)))

#define MACRO_HEX_TO_BCD(x) ((BYTE) (((x / 10 ) << 4) | ((x % 10))))


1

这里的真正问题是基数和单位的混淆。

202 应该是十六进制,相当于 514 十进制... 因此 BCD 计算是正确的。

二进制码十进制将把十进制数(514)转换为三个半字节大小的字段: - 5 = 0101 - 1 = 0001 - 4 = 0100

更大的问题是您把标题搞反了,并且您正在将 Uint 转换为 BCD,而标题要求将 BCD 转换为 Uint。


安德鲁,这并不像你想的那么复杂。没有必要将十六进制转换为十进制来查找BCD值。 - Daniel Gehriger
不过我认为它解释了混淆之处... 0x202 等于 514(BCD码),而 202(十进制)等于 202(BCD码)。 - Andrew
@Andrew,相反的是这样的,0x202 == 202(BCD)。 - nos
嗯?让我们更简单一些... 10d = 0Ah = (0001 0000)BCD == "10"bcd 而不是 "0A"bcd... 那么 0x202 怎么能等于 202 BCD 呢? - Andrew
@Andrew 10d = 0Ah 的二进制表示为0000 1010,所以我猜这取决于他的202是BCD、十进制还是十六进制。当然,uiValue = 202 是错误的,尽管文本说它应该是十六进制:输入:202(十六进制) 如果输入是0x202,如文本所说,那么输入的二进制表示为0000 0010 0000 0010,我们可以轻松地将其转换为BCD。 - nos
10 == 0x0A 的二进制表示是1010,但是二进制编码十进制表示为0001-0000。这两个不相同。 - Andrew

0
您还可以尝试以下操作:
在每次迭代中,余数(表示为四位二进制数)被放置在相应的位置。
uint32_t bcd_converter(int num)
{                          
  uint32_t temp=0;              
  int i=0;                 
  while(num>0){            
    temp|=((num%10)<<i);   
    i+=4;                  
    num/=10;               
  }                        
                           
  return temp;             
}                          


0
long bin2BCD(long binary) { // double dabble: 8 decimal digits in 32 bits BCD
  if (!binary) return 0;
  long bit = 0x4000000; //  99999999 max binary
  while (!(binary & bit)) bit >>= 1;  // skip to MSB

  long bcd = 0;
  long carry = 0;
  while (1) {
    bcd <<= 1;
    bcd += carry; // carry 6s to next BCD digits (10 + 6 = 0x10 = LSB of next BCD digit)
    if (bit & binary) bcd |= 1;
    if (!(bit >>= 1)) return bcd;
    carry = ((bcd + 0x33333333) & 0x88888888) >> 1; // carrys: 8s -> 4s
    carry += carry >> 1; // carrys 6s  
  }
}

0
一个天真但简单的解决方案:
char buffer[16];
sprintf(buffer, "%d", var);
sscanf(buffer, "%x", &var);

这不会产生十六进制,而OP要求BCD码,它们并不等价。虽然我没有投反对票,但是是别人投的。 - fkl
从他的第一个修订版问题中可以明显看出,他想要将202转换为514,这正是它所做的。 - panda-34
但他想要的是514BCD而不是514Hex。 - Andrew
2
@nos,我不知道该回答标题中的问题,还是在问题主体中,抑或是样例代码中。它们三者是不同的。 - panda-34
@panda-34:就我个人而言,我喜欢你的解决方案 - 而且鉴于OP对他所接受的答案的评论,这个解决方案是正确的。 - Daniel Gehriger
显示剩余2条评论

0

这是我开发的解决方案,非常适用于嵌入式系统,如Microchip PIC微控制器:

#include <stdio.h>
void main(){
    unsigned int output = 0;
    unsigned int input;
    signed char a;
    //enter any number from 0 to 9999 here:
    input = 1265;
    for(a = 13; a >= 0; a--){
        if((output & 0xF) >= 5)
            output += 3;
        if(((output & 0xF0) >> 4) >= 5)
            output += (3 << 4);
        if(((output & 0xF00) >> 8) >= 5)
            output += (3 << 8);
        output = (output << 1) | ((input >> a) & 1);
    }
    printf("Input decimal or binary: %d\nOutput BCD: %X\nOutput decimal: %u\n", input, output, output);
}

0

简单的解决方案

#include <stdio.h>

int main(void) {

   int binaryInput = 514 ;      //0x202 
   int bcdResult = 0;
   int digit = 0;
   int i=1;

   printf("Binary: 0x%x (dec: %d)\n", binaryInput , binaryInput );

   while (binaryInput > 0) {
 
      digit = binaryInput %10;          //pick digit
      bcdResult = bcdResult+digit*i;
      i=16*i;
      binaryInput = binaryInput/ 10;
   }
   printf("BCD: 0x%x (dec: %d)\n", bcdResult , bcdResult );
   return 0;
}

二进制:0x202(十进制:514)

BCD码:0x514(十进制:1300)


0

这是我对 n 字节转换的版本:

//----------------------------------------------
// This function converts n bytes Binary (up to 8, but can be any size)
// value to n bytes BCD value or more.
//----------------------------------------------

void bin2bcdn(void * val, unsigned int8 cnt)
{
    unsigned int8  sz, y, buff[20];         // buff = malloc((cnt+1)*2);
    
    if(cnt > 8) sz = 64;                    // 8x8
    else        sz = cnt * 8 ;              // Size in bits of the data we shift
    
    memset(&buff , 0, sizeof(buff));        // Clears buffer
    memcpy(&buff, val, cnt);                // Copy the data to buffer

    while(sz && !(buff[cnt-1] & 0x80))      // Do not waste time with null bytes,
    {                                       // so search for first significative bit
        rotate_left(&buff, sizeof(buff));   // Rotate until we find some data
        sz--;                               // Done this one
    }
    while(sz--)                             // Anyting left?
    {
        for( y = 0; y < cnt+2; y++)         // Here we fix the nibbles
        {
            if(((buff[cnt+y] + 0x03) & 0x08) != 0) buff[cnt+y] += 0x03;
            if(((buff[cnt+y] + 0x30) & 0x80) != 0) buff[cnt+y] += 0x30;
        }
        rotate_left(&buff, sizeof(buff));   // Rotate the stuff
    }
    memcpy(val, &buff[cnt], cnt);           // Copy the buffer to the data
//  free(buff);       //in case used malloc
}   // :D Done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接