无符号整数转BCD码?

7

我知道你可以使用这个表格将十进制转换为BCD:

0 0000

1 0001

2 0010

3 0011

4 0100

5 0101

6 0110

7 0111

8 1000

9 1001

是否有一个公式可以用于此转换,或者只能使用表格? 我正在尝试编写一些代码来进行此转换,但我不确定如何进行计算。 有什么建议吗?


你想进行什么类型的转换?数字 5 已经是 0101,不需要任何转换,70111 等等。 - LukeH
是的。这个问题需要更精确的描述。BCD 可以有很多含义。它是每字节一个十进制数字吗?还是每字节两个十进制数字? - dan-gph
具有本地BCD支持的处理器(例如m68k系列)通常(一般而言?)使用每字节两个十进制数字。 - dmckee --- ex-moderator kitten
你可以使用压缩的BCD编码,将两个数字压缩在一个字节中,例如:99 -> 10011001b。也可以使用非压缩的BCD编码,将一个数字存储在一个字节中,例如:9 -> 00001001b。当你想要为此编写代码时,请记住这一点。 - Agguro
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;
}

1
我非常喜欢这个。非常干净。RX: char * dec2bcd(char *dest, uint16t src)其中dec2bcd()将结果中使用的nybbles复制到dest中,并像大多数C标准库函数一样返回dest。USHRT_MAX只需要5个nybbles,即3个字节。这是一种更快的方法,但就其所涉及的范围而言,我非常喜欢你的方法。https://dev59.com/lXzaa4cB1Zd3GeqPKgIM - user1899861

8

你知道二进制数系统吧?

特别是要看看这一章节

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

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

3
需要翻译的内容:It should also be noted that ASCII representations for the numerals are also in BCD (on purpose)...well at least the lower nybble.ASCII表示数字的方式也是BCD编码(有意为之)……至少是低位半字节。 - KFro
2
额...KFro。这不是非常清楚。您的意思是ASCII表示中数字的低四位对应于数字的BCD表示吗? - dmckee --- ex-moderator kitten
1
@dmckee:是这样的,所以我认为KFro的意思是:"0"是0x30,"1"是0x31等。 - LukeH
ASCII的数字与BCD不等价,但EBCDIC是等价的。例如:在EBCDIC表中的前10个条目是BCD数字0-9,其中最高有效半字节为0b0000,而在ASCII中它们偏移了十六进制30,即ASCII '0' == 0x30在ASCII表中。 - user1899861
1
@RocketRoy,那其实不是真的。EBCDIC数字的条目范围在0xf00xf9之间。 - fuz
@FUZxxl,我承认我的错误。你是对的。0xf0到0xf9!问题描述得太差了,我无法确定OP在问什么,但如果是ASCII到BCD,则从字符中减去0x30并将最后4位推入缓冲区,直到数字用完。一个好的方法是使用“0”而不是0x30。当别人在5年后阅读您的代码时,可以更容易地理解正在进行的操作。这个答案并不是很有帮助。将两个nybbles推入一个char,然后将chars连接成“字符串”的代码效果很好,而且很干净。 - user1899861

7

这来自于微控制器世界... 请注意在除法中数值是四舍五入的。例如,将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) );
}

2
聪明。如果写成 return ( ((val/10)<<4) + (val%10)); 就更清晰了,因为这样可以清楚地看到乘以16实际上是左移4位,将第一个nybble移动到字节中最高有效位置。 - user1899861

5
通常当有人说他们想将十进制转换为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。

1
仅使用字节中的尾部nybble来进行BCD转换非常低效。MySQL多年前就放弃了这种方法,最近也完全放弃了十进制数据的BCD转换。您可以轻松地将否则浪费的前导nybble左移一个字节,同时将第二个数字推入它。然后只需将双nybble /字节/字符连接到字符数组即可。简单、高效、直接明了。 - user1899861

2
如果您想每个字节有两位小数,并且“unsigned”是“unsigned long”的一半大小(如果需要,请使用uint32和uint64 typedefs):
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位也不会提前停止,这将允许优化器展开它,但如果您的数字经常很慢,则速度较慢。

0

简单化它。

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

0

像这样的东西对于你的转换是否有效?

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

0

这段代码进行编码和解码。以下是基准测试结果。

  • 往返需要 45 个时钟周期
  • 将 BCD 转换为 uint32_t 需要 11 个时钟周期
  • 将 uint32_t 打包成 BCD 需要 34 个时钟周期

我在这里使用了 uint64_t 来存储 BCD。非常方便且固定宽度,但对于大型表格来说不太空间有效。为此,请将 BCD 数字打包成 char[] 的 2 位。

// -------------------------------------------------------------------------------------
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的倍数进行乘法运算 - 这正好具有所需的效果。而且速度更快。想想看。


很难从表面上判断某些事情是否不可能。 - Chai T. Rex

0

我知道这个问题之前已经有答案了,但是我使用模板来构建特定代码,以处理不同大小的无符号整数。

#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 :
//  https://dev59.com/iEnSa4cB1Zd3GeqPMk3p
//  &&
//  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;
}

0
这是一个针对uint16_t的宏,以便在编译时进行评估(前提是u是预定义常量)。这与上面的dec2bcd()一致,最高支持到9999。
#define U16TOBCD(u) ((((u/1000)%10)<<12)|(((u/100)%10)<<8)|\
                    (((u/10)%10)<<4)|(u%10))

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