【问题标题】:Unsigned Integer to BCD conversion?无符号整数到 BCD 转换?
【发布时间】:2009-09-11 00:02:02
【问题描述】:

我知道您可以使用此表将十进制转换为 BCD:

0 0000

1 0001

2 0010

3 0011

4 0100

5 0101

6 0110

7 0111

8 1000

9 1001

这种转换是否有公式,或者您必须使用表格?我试图为这种转换编写一些代码,但我不确定如何计算它。有什么建议吗?

【问题讨论】:

  • 您要进行什么样的转换?数字5已经是0101,不需要任何转换,70111等等。
  • 是的。这个问题需要更精确。 BCD 可能意味着很多事情。每个字节是一个十进制数字吗?每个字节有两个十进制数字?
  • 支持原生 BCD 的处理器(如 m68k 系列)通常(通常?)使用两位十进制数字表示字节。
  • 您可以打包 BCD,一个字节中的两位数:例如:99 -> 10011001b 并解压,这是一个字节中的一位数,例如:9 -> 00001001b。当您想为其编写一些代码时,请记住这一点。

标签: c++ bcd


【解决方案1】:

你知道Binary numeral system,不是吗?

特别是看看this chapter

编辑:还要注意 KFro 的评论,即数字的二进制 ASCII 表示的低半字节(= 4 位)是 BCD。这使得转换 BCD ASCII 非常容易,因为您只需添加/删除前 4 位:

数字 ASCII 码 0 0011 0000 1 0011 0001 ... 8 0011 1000 9 0011 1001

【讨论】:

  • 还应该注意的是,数字的 ASCII 表示也是 BCD(故意)......至少是低位。
  • 呃...KFro。这不是很清楚。你的意思是数字的 ASCII 表示的低半字节对应于数字的 BCD 表示?
  • @dmckee:就是这样,所以我假设这就是 KFro 的意思:"0"0x30"1"0x31 等等。
  • ASCII 的数字不是 BCD 等价物,但 EBCDIC 是。 IE:EBCDIC 表中的前 10 个条目是 BCD 数字 0-9,其中最重要的半字节是 0b0000,而在 ASCII 中它们是偏移十六进制 30,IE:ASCII 表中的 ASCII '0' == 0x30。跨度>
  • @RocketRoy 这实际上不是真的。数字的 EBCDIC 条目在 0xf00xf9 的范围内。
【解决方案2】:
#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;
}

【讨论】:

  • 我非常喜欢这个。很干净。 RX: char * dec2bcd(char *dest, uint16t src) 其中 dec2bcd() 将 result 中使用的 nybbles 复制到 dest 并像大多数 C 标准库函数一样返回 dest。 USHRT_MAX,只需要 5 个 nybbles,也就是 3 个字节。这是一种更快的方法,但我非常喜欢你的方法——就它而言。 stackoverflow.com/questions/21501815/…
【解决方案3】:

这是来自微控制器世界....请注意,除法中的值是四舍五入的。例如 91 到 BCD 将是 91/10 * 16 = 144 + 91%10 = 145。转换为二进制是 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) );
}

【讨论】:

  • 聪明。如果写成 return (((val/10)
【解决方案4】:

通常当有人说他们想从十进制转换为 BCD 时,他们指的是不止一位十进制数字。

BCD 通常被打包成每个字节的两个十进制数字(因为 0..9 适合 4 位,正如您所展示的),但我认为使用字节数组更自然,每个十进制数字一个。

一个 n 位无符号二进制数将适合 ceil(n*log_2(10)) = ceil(n/log10(2)) 十进制数字。它也适合 ceil(n/3) = floor((n+2)/3)) 十进制数字,因为 2^3=8 小于 10。

考虑到这一点,下面是我获取无符号整数的十进制数字的方法:

#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;
}

当然,如果您知道 int 类型的宽度,您可能更喜欢固定长度的数组。如果您还记得第 0 位是最低有效位并且仅在输入/输出上反转这一事实,那么根本没有理由反转。在您不使用固定位数的情况下,将最低有效位保留为第一位可以简化逐位算术运算。

如果您想将“0”表示为单个“0”十进制数字而不是空数字字符串(两者都有效),那么您需要专门检查 x==0。

【讨论】:

  • BCD 在一个字节中仅使用尾随 nybble 是非常低效的。 MySQL 几年前放弃了这种方法,最近,BCD 完全用于十进制数据。您可以轻松地将原本浪费的前导 nybble 左移一个字节,同时将第二个数字推到它上面。然后只需将双 nybble/byte/char 连接到 char 数组中。简单、高效、直接。
【解决方案5】:

如果您希望每个字节有两个十进制数字,并且“unsigned”是“unsigned long”大小的一半(如果需要,请使用 uint32 和 uint64 typedef):

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;
}

这会在最低有效半字节中留下最低有效(单位)十进制数字。您还可以将循环执行固定次数(对于 uint32 为 10),当只剩下 0 位时不会提前停止,这将允许优化器展开它,但如果您的数字通常很慢,那就更慢了。

【讨论】:

    【解决方案6】:

    这样的事情对您的转化有用吗?

    #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> >();
    }
    

    【讨论】:

      【解决方案7】:

      此代码进行编码和解码。基准如下。

      • 往返 45 个时钟
      • 11 个时钟用于将 BCD 解压为 uint32_t
      • 34 个时钟用于将 uint32_t 打包成 BCD

      我在这里使用了一个 uint64_t 来存储 BCD。非常方便且固定宽度,但对于大桌子来说空间效率不是很高。为此,将 BCD 数字 2 打包到 char[] 中。

      // -------------------------------------------------------------------------------------
      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;
      }
      

      注意: 即使使用 64 位整数,似乎也不可能左移超过 32 位,但幸运的是,完全有可能乘以 16 的某个因子——这很高兴达到了预期的效果。它也快得多。去图吧。

      【解决方案8】:

      我知道这个问题之前已经回答过了,但我已经使用模板将其扩展为不同大小的无符号整数来构建特定代码。

      #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;
      }
      

      【讨论】:

        【解决方案9】:

        这是 uint16_t 的宏,因此它在编译时被评估(假设 u 是一个预定义的常量)。这与 dec2bcd() 从上面到 9999 一致。

        #define U16TOBCD(u) ((((u/1000)%10)<<12)|(((u/100)%10)<<8)|\
                            (((u/10)%10)<<4)|(u%10))
        

        【讨论】:

          【解决方案10】:

          只是简化了。

          #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);
          }
          

          【讨论】:

            猜你喜欢
            • 2013-11-16
            • 1970-01-01
            • 2013-02-16
            • 1970-01-01
            • 2012-01-09
            • 1970-01-01
            • 2017-07-21
            • 1970-01-01
            相关资源
            最近更新 更多