我一直在寻找C99中在任意位长的缓冲区上进行位移位的函数。可能有很多库在执行此操作,但是我想避免为此添加依赖项(但是我可以从库中提取函数,而无需提取过多代码)。 至关重要的是,它应该能够就地工作,并且不会损坏超出指定位长的任何内容。
为澄清歧义,问题是如何实现以下功能:
/**
* shift a buffer right with bit granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param size length in bits of src/dst
* @param shift shift amount in bits
* @param fill fill value (boolean) for the MSBs
*
* shift is allowed to be larger than size, it behaves like they are equal
*/
void bufshrbits(void* dst,const void* src,size_t size, size_t shift, bool fill);
/**
* shift a buffer left with bit granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param size length in bits of src/dst
* @param shift shift amount in bits
* @param fill fill value (boolean) for the LSBs
*
* shift is allowed to be larger than size, it behaves like they are equal
*/
void bufshlbits(void* dst,const void* src,size_t size, size_t shift, bool fill);
SO问题Bitwise shifting array of char's的标题不正确,实际上是在要求轮换。
答案 0 :(得分:0)
我想到了以下内容,并在这里进行了测试:https://wandbox.org/permlink/g3FDe88vKgdPsGLC 它可以在任何CPU上工作,因为它仅使用字节访问(尽管未在big endian平台上进行测试)。
#include <stdint.h>
#include <stdbool.h>
/**
* shift a buffer left with byte granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param byte_size length in bytes of src/dst
* @param byte_shift shift amount in bytes
* @param fill8 fill value for the LSBs
*
* byte_shift is allowed to be larger than byte_size, it behaves like they are equal
*/
static void bufshl(void*const dst,const void*const src,size_t byte_size, size_t byte_shift, uint8_t fill8){
if(0==byte_size) return;
if(byte_shift>byte_size) byte_shift=byte_size;
uint8_t*const dst8=(uint8_t*)dst;
const uint8_t*const src8=(const uint8_t*const)src;
for(size_t i=byte_size-1;i!=byte_shift-1;i--){dst8[i] = src8[i-byte_shift];}
for(size_t i=0;i<byte_shift;i++){dst8[i] = fill8;}
}
/**
* shift a buffer left with bit granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param size length in bits of src/dst
* @param shift shift amount in bits
* @param fill fill value for the LSBs
*
* shift is allowed to be larger than size, it behaves like they are equal
*/
static void bufshlbits(void*const dst,const void*const src,size_t size, size_t shift, bool fill){
if(0==size) return;
const uint8_t fill8 = fill ? 0xFF : 0x00;
if(shift>size) shift=size;
uint8_t*const dst8=(uint8_t*)dst;
const uint8_t*const src8=(const uint8_t*const)src;
const size_t byte_size = (size+7)/8;
const unsigned int byte_shift=shift%8;
const unsigned int cshift = (8-byte_shift)%8;
const uint8_t last = src8[byte_size-1];
const size_t lsb = shift/8;
if(0==(shift%8)){
bufshl(dst,src,byte_size,lsb,fill8);
} else {
uint8_t carry=src8[byte_size-1-lsb];
for(size_t i=byte_size-1;i!=lsb-1;i--){
const size_t sidx = i-1-lsb;
const uint8_t s = sidx>byte_size ? fill8 : src8[sidx];
const uint8_t d = (carry<<byte_shift)|(s >> cshift);
carry = src8[sidx];
dst8[i] = d;
}
}
for(size_t i=0;i<lsb;i++){dst8[i]=fill8;}
if(size%8){
const uint8_t last_mask = 0xFF<<(size % 8);
dst8[byte_size-1] &= ~last_mask;
dst8[byte_size-1] |= last & last_mask;
}
}
/**
* shift a buffer right with byte granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param byte_size length in bytes of src/dst
* @param byte_shift shift amount in bytes
* @param fill8 fill value for the MSBs
*
* byte_shift is allowed to be larger than byte_size, it behaves like they are equal
*/
static void bufshr(void*const dst,const void*const src,size_t byte_size, size_t byte_shift, uint8_t fill8){
if(0==byte_size) return;
if(byte_shift>byte_size) byte_shift=byte_size;
uint8_t*const dst8=(uint8_t*)dst;
const uint8_t*const src8=(const uint8_t*const)src;
const size_t last=byte_size-byte_shift;
for(size_t i=0;i!=last;i++){dst8[i] = src8[i+byte_shift];}
for(size_t i=last;i<byte_shift;i++){dst8[i] = fill8;}
}
/**
* shift a buffer right with bit granularity (little endian)
*
* @param dst destination buffer, can be equal to src
* @param src source buffer
* @param size length in bits of src/dst
* @param shift shift amount in bits
* @param fill fill value for the MSBs
*
* shift is allowed to be larger than size, it behaves like they are equal
*/
static void bufshrbits(void*const dst,const void*const src,size_t size, size_t shift, bool fill){
if(0==size) return;
const uint8_t fill8 = fill ? 0xFF : 0x00;
if(shift>size) shift=size;
uint8_t*const dst8=(uint8_t*)dst;
const uint8_t*const src8=(const uint8_t*const)src;
const size_t byte_size = (size+7)/8;
const unsigned int bshift=shift%8;
const unsigned int cshift = bshift ? (8-bshift)%8 : 8;
const uint8_t last = src8[byte_size-1];
const size_t lsb = shift/8;
if((0==(shift%8)) && (0==(size%8))) {
bufshr(dst,src,byte_size,lsb,fill8);
} else {
const uint8_t last_mask = size%8 ? 0xFF<<(size % 8) : 0;
uint8_t carry = lsb+1 >=byte_size ? fill8 : src8[lsb+1];
if(lsb+1 == byte_size-1) {
carry &= ~last_mask;
carry |= last_mask & fill8;
}
if(byte_size>lsb){
for(size_t i=0;i<byte_size-lsb-1;i++){
const size_t sidx = i+lsb;
uint8_t s;
if(sidx>=byte_size-1){
s=(src8[sidx] & ~last_mask) | (last_mask & fill8);
}else{
s=src8[sidx];
}
const uint8_t d = (carry<<cshift)|(s >> bshift);
carry = sidx+2 >=byte_size? fill8 : src8[sidx+2];
if(sidx+2 == byte_size-1) {
carry &= ~last_mask;
carry |= last_mask & fill8;
}
dst8[i] = d;
}
}
const size_t sidx = byte_size-lsb-1+lsb;
carry &= ~last_mask;
carry |= last_mask & fill8;
uint8_t s;
if(sidx>=byte_size-1){
s=(src8[sidx] & ~last_mask) | (last_mask & fill8);
}else{
s=src8[sidx];
}
const uint8_t d = (carry<<cshift)|(s >> bshift);
dst8[byte_size-lsb-1] = d;
}
for(size_t i=byte_size-lsb;i<byte_size;i++){dst8[i]=fill8;}
if(size%8){
const uint8_t last_mask = 0xFF<<(size % 8);
dst8[byte_size-1] &= ~last_mask;
dst8[byte_size-1] |= last & last_mask;
}
}
答案 1 :(得分:-1)
您可以通过调整索引一步将大粒度和小粒度的偏移结合起来。例如,要进行18位右移,您将从index
和index - 2
的字节中得到index - 3
的字节的位。
// ...┆abcd...┆ ┆ ┆ ┆ ┆... ← src
// | <- 18-bit -> |
// ...┆ ┆ ┆..abcd.┆ ┆ ┆... ← dst
// ...₀₁₂₃₄₅₆₇⁰¹²³⁴⁵⁶⁷₀₁₂₃₄₅₆₇⁰¹²³⁴⁵⁶⁷₀₁₂₃₄₅₆₇ ... ← bit position
这是右移的示例代码。为简单起见,我选择字节作为大的单位移位,但是为了获得良好的性能,每个单位应为32或64位字,或者甚至更好地使用SIMD一次处理128/256/512位。这就是为什么我对单位使用“肢体”,这是GMP表示大整数中每个数字的术语,因此它不取决于单位大小,并且更易于扩展。只需将char
更改为uint64_t
或进行类似的更改
// dst = src >> shift
void memshrbits(void* const dst, const void* const src,
size_t size, size_t shift, int fill)
{
char *d = (char*)dst, *s = (char*)src;
const size_t limbWidth = CHAR_BIT * sizeof(*d); // number of bits in a limb
assert(shift < limbWidth); // or just fill the whole dst with `fill`
// when shift >= limbWidth
const size_t bitShift = shift % limbWidth; // number of bits to shift in a limb
const size_t limbShift = shift / limbWidth; // number of limbs to shift
size_t srcLength = size / limbWidth;
size_t numberOfRemainingBits = size % limbWidth;
// backup the last bits in case `size` is odd
size_t remainingBits = s[srcLength - 1] & ((1U << numberOfRemainingBits) - 1);
fill = -fill; // all ones or zero bits
size_t i = srcLength - 1;
// Filling dst from the right, because we're shifting right
for (; i > limbShift; --i)
d[i] = (s[i - limbShift ] >> bitShift) |
(s[i - limbShift - 1] << (limbWidth - bitShift));
d[i--] = (s[0] >> bitShift) | (fill << (limbWidth - bitShift));
// The remaining bits are blank spaces and will be filled with `fill`
for (; i != (size_t)(-1); --i)
d[i] = fill;
// Restore the bits if shift is odd
d[srcLength - 1] = (d[srcLength - 1] & (-1U << numberOfRemainingBits)) |
remainingBits;
}
如果size
始终是单位的倍数(在这种情况下,即8的倍数),则更容易,因为您不必处理src
末尾的奇数位,并且可以删除remainingBits
个部分
对左移执行相同操作,但在相反方向进行迭代