如果我有两个打包的BCD格式的数字并且想要添加它们,这是一个很好的方法来添加它们:将两个数字转换为整数,执行正常的整数加法,然后将结果转换回BCD?< / p>
答案 0 :(得分:1)
下面的C99代码添加了压缩的BCD操作数,其中8个BCD数字存储在uint32_t
中。通过选择uint64_t
来处理16个BCD数字,可以轻松地将此代码扩展到更宽的BCD操作数。由于这种方法依赖于位并行处理,因此对于窄打包BCD操作数可能效率不高。
在压缩BCD格式中,每个BCD数字占用无符号整数操作数的一个半字节(4位组)。如果蚕食加法导致总和> 9,我们希望进入下一个更高的蚕食。如果我们使用常规整数加法来添加两个打包的BCD操作数,则当半字节和> 1时,将不会发生期望的半字节携带。 9,但是&lt; 16.为了解决这个问题,我们可以为每个半字节总和增加6个。
我们可以找到如下的半字节:两个整数x
,y
的逐位和为x ^ y
。在常规整数加法期间从下一个较低位位置进位的任何位位置,x ^ y
和x + y
中的位将不同。所以我们可以找到带有进位的位(x ^ y) ^ (x + y)
。我们对进位的位4,8,...,32感兴趣,它们是位3,7,...,31的进位。
如果从第31位到第32位存在进位,则存在轻微问题,因为uint32_t
个操作数仅保持32位。如果我们发现两个无符号整数的总和小于任何一个加数,我们就可以检测到这个。当操作七位操作数而不是八位操作数时,可以省略处理第31位进位的三个操作。
/* Add two packed BCD operands, where each uint32_t holds 8 BCD digits */
uint32_t bcd_add (uint32_t x, uint32_t y)
{
uint32_t t0, t1;
t0 = x + 0x66666666; // force nibble carry when BCD digit > 9
t1 = x ^ y; // bit-wise sum
t0 = t0 + y; // addition with nibble carry
t1 = t1 ^ t0; // (x ^ y) ^ (x + y)
t0 = t0 < y; // capture carry-out from bit 31
t1 = (t1 >> 1) | (t0 << 31); // nibble carry-outs in bits 3, 7, ..., 31
t0 = t1 & 0x88888888; // extract nibble carry-outs
t1 = t0 >> 2; // 8 - (8 >> 2) = 6
return x + y + (t0 - t1); // add 6 to any digit with nibble carry-out
}
Knuth, TAOCP Vol.4A第1部分,在第7.1.3节的练习100的答案中提供了一个卓越的解决方案(需要更少的操作)。此变体特别适用于具有可以评估三个参数的任何逻辑函数的指令的处理器体系结构,例如现代NVIDIA GPU的LOP3
指令。
uint32_t median (uint32_t x, uint32_t y, uint32_t z)
{
return (x & (y | z)) | (y & z);
}
uint32_t bcd_add_knuth (uint32_t x, uint32_t y)
{
uint32_t z, u, t;
z = y + 0x66666666;
u = x + z;
t = median (~x, ~z, u) & 0x88888888;
return u - t + (t >> 2);
}