gray_code
类。我最终将发布实现的几个部分,但是我想回顾的主要是附加功能。无论如何,这是类要点的代码:如您所见,gray_code<T>
只是无符号内置类型T
的包装。主要操作是T
类型的构造和转换;借助公共变量value
可以访问基础值。此类的目的是易于使用。多亏了转换运算符,无论何时可以使用相应的整数,都可以使用gray_code
。我的目标之一是仅在gray_code
运算比将格雷码转换为相应的整数,对整数执行运算并将整数转换回格雷码的速度更快时提供操作。转换确实非常快,因此比双转换快的操作很难编写。我确实写了其中的一些,但未包括在其中(比较,按位运算,转换为bool
,递增和递减;您可以假定其余函数在gray_code
s之间都可以正常工作),而我目前正在努力进行加法运算。 我的加法算法基于RW Doran(我是唯一想到Dorian Gray的人)在本文中提供的伪代码算法,并归因于Harold Lucal。这是原始的伪代码加法算法(
⊕
表示xor
):template<typename Unsigned>
struct gray_code
{
static_assert(std::is_unsigned<Unsigned>::value,
"gray code only supports built-in unsigned integers");
// Underlying unsigned integer
using value_type = Unsigned;
// Variable containing the gray code
value_type value;
////////////////////////////////////////////////////////////
// Constructors operations
// Default constructor
constexpr gray_code() noexcept:
value(0u)
{}
/**
* @brief Construction from an unsigned integer.
*
* The integer is converted to gray code. The value
* is preserved. The representation is not.
*
* @param value Unsigned integer to convert
*/
constexpr explicit gray_code(value_type value) noexcept:
value( (value >> 1) ^ value )
{}
////////////////////////////////////////////////////////////
// Assignment operations
/**
* @brief Assigns an unsigned integer.
*
* It works the same way as the equivalent
* constructor does.
*/
auto operator=(value_type other) & noexcept
-> gray_code&
{
value = (other >> 1) ^ other;
return *this;
}
////////////////////////////////////////////////////////////
// Conversion operations
/**
* @brief Conversion to the underlying type.
*
* See http://www.dspguru.com/dsp/tricks/gray-code-conversion
*/
operator value_type() const noexcept
{
value_type res = value;
for (value_type mask = std::numeric_limits<value_type>::digits / 2
; mask ; mask >>= 1)
{
res ^= res >> mask;
}
return res;
}
};
我将算法翻译成C ++。这是朴素的实现(您可以在此处找到相应的C版本):
procedure add (n: integer; A,B:word; PA,PB:bit;
var S:word; var PS:bit; var CE, CF:bit);
var i: integer; E, F, T: bit;
begin
E := PA; F := PB;
for i:= 0 to n-1 do begin {in parallel, using previous inputs}
S[i] := (E and F) ⊕ A[i] ⊕ B[i];
E := (E and (not F)) ⊕ A[i];
F := ((not E) and F) ⊕ B[i];
end;
CE := E; CF := F;
end;
该算法有效。但是,它不如将格雷码转换为整数,执行常规加法并将结果转换回格雷码那样有效。
如果需要,这里是
is_odd
的实现,尽管它不会增加太多的开销,因为它只执行一次。格雷码的奇偶校验对应于设置的位数的奇偶校验。该函数在可能的情况下使用编译器内部函数(在某些体系结构上可能像在奇偶校验位上那样简单)和\ $ O(\ logn)\ $算法对位数进行计数。template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
-> gray_code<Unsigned>
{
// parity of lhs and rhs
bool lhs_p = is_odd(lhs);
bool rhs_p = is_odd(rhs);
gray_code<Unsigned> res{};
for (Unsigned i{} ; i < std::numeric_limits<Unsigned>::digits ; ++i)
{
// Get the ith bit of rhs and lhs
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
// Copy lhs_p and rhs_p (see {in parallel} in the original algorithm)
bool lhs_p_cpy = lhs_p;
bool rhs_p_cpy = rhs_p;
// Set the ith bit of res
Unsigned res_i = (lhs_p_cpy & rhs_p_cpy) ^ lhs_i ^ rhs_i;
res |= (res_i << i);
// Update e and f
lhs_p = (lhs_p_cpy & (!rhs_p_cpy)) ^ lhs_i;
rhs_p = ((!lhs_p_cpy) & rhs_p_cpy) ^ rhs_i;
}
return res;
}
注意:如果您发现此问与答很有趣,并且在另一种添加格雷码的算法中很有趣,那么我将对自定义算法进行另一次问答,该算法基于\ $ 2 \ $分解的幂来添加格雷码。 br />
#1 楼
循环不变代码运动这是大多数编译器执行的优化的名称:当他们检测到实际上不依赖于循环状态的代码时,便将其移出循环。它不像通常的不变式那样明显,但让我们看一下这行:
Unsigned res_i = (lhs_p_cpy & rhs_p_cpy) ^ lhs_i ^ rhs_i;
lhs_i
和rhs_i
对应于\ $ i_ {th} \ $位lhs
和rhs
的这些位与循环无关(循环不会更改它们),并且可以在运行循环之前通过执行lhs ^ rhs
一次计算所有这些位。因此,我们可以通过将其移出循环来简化功能。如果我们将它们直接存储在res
中,我们甚至可以将对res
的分配简化为一个简单的res ^= res_i << i
:不幸的是,lhs_i
和rhs_i
也被用来计算新的lhs_p
和rhs_p
的值,因此我们无论如何都无法将它们从循环主体中删除。如果我们要进一步优化算法,我们还需要其他东西,例如... 更有效的奇偶校验算法
由于仅使用
is_odd
,因此几乎没有任何改变在算法的最开始,但是当无法使用__builtin_parity
时,其后备实现不是最佳的。实际上,无符号整数的奇偶校验可以通过对每个位进行xor
运算而获得,而不必计算设置的位数。整个xor
的位运算可以使用以下算法“并行”完成,而不是一点一点地进行:template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
-> gray_code<Unsigned>
{
// parity of lhs and rhs
bool lhs_p = is_odd(lhs);
bool rhs_p = is_odd(rhs);
gray_code<Unsigned> res = lhs ^ rhs;
for (Unsigned i{} ; i < std::numeric_limits<Unsigned>::digits ; ++i)
{
// Get the ith bit of rhs and lhs
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
// Copy lhs_p and rhs_p (see {in parallel} in the original algorithm)
bool lhs_p_cpy = lhs_p;
bool rhs_p_cpy = rhs_p;
// Set the ith bit of res
Unsigned res_i = lhs_p_cpy & rhs_p_cpy;
res ^= res_i << i;
// Update e and f
lhs_p = (lhs_p_cpy & (!rhs_p_cpy)) ^ lhs_i;
rhs_p = ((!lhs_p_cpy) & rhs_p_cpy) ^ rhs_i;
}
return res;
}
该算法将
xor
的无符号整数的前半部分与后半部分相乘,然后对结果重复操作...,直到xor
仅剩两位为止,从而导致比填充计数更有效的操作。该页面甚至提供了算法的更微妙的实现,以“跳过”最后的迭代。我并没有真正使用魔术数字来研究建议的实现,因为我希望我的代码仍然可以使用任何大小的整数。摆脱临时成员
可能最慢的灰色加法是
for
循环的主体。为了找到可能的优化方法,我决定进一步去除lhs_p_cpy
和rhs_p_cpy
,它们是计算lhs_p
的新值所需的rhs_p
和rhs_p
的副本。通过简单地用rhs_p_cpy
替换它很容易摆脱rhs_p
,但是摆脱lhs_p_cpy
却比较困难,因为lhs_p
已经同时更新了。毫不费力地,下面的循环体是我们可以获得的最好的循环体:for (std::size_t i = std::numeric_limits<Unsigned>::digits / 2u ; i ; i >>= 1u)
{
code.value ^= (code.value >> i);
}
return bool(code.value & 1u);
因此,我决定构建一些真值表。在下表中,\ $ lhs_ {old} \ $和\ $ rhs_ {old} \ $是更新前
lhs_p
和rhs_p
的可能值,而\ $ lhs_ {new} \ $和\ $ rhs_ {new} \ $是更新后相同变量的值。我对lhs_i
和rhs_i
没有任何兴趣,因为它们都不用于计算\ $ lhs_ {new} \ $和\ $ rhs_ {new} \ $;因此,在剩下的反射中,我将忽略它们。\ begin {array} {| cc | cc |}
\ hline
lhs_ {old}和rhs_ { old}&lhs_ {new}&rhs_ {new} \\
\ hline
0&0&0&0 \\
0&1&0&1 \\
1&0&1&0 \\
1&1&0&0 \\
\ hline
\ end {array}
我们可以在\ $ lhs_ {new} \ $的计算中轻松地用\ $ lhs_ {old} \ $替换
lhs_p_cpy
,但是由于更新已经发生,因此我们不能用它来计算\ $ rhs_ {new} \ $。我最初想摆脱lhs_p_cpy
的想法是“我们可以仅用\ $ rhs_ {old} \ $和\ $ lhs_ {new} \ $来计算\ $ rhs_ {new} \ $吗?”。查看上面的真值表,似乎不可能。尝试在\ $ lhs_ {new} \ $之前计算\ $ rhs_ {new} \ $也不能解决问题。但是,我们已经在循环中计算了另一个值:
res_i
。因此,我在表中注入了\ $ res_i \ $,并研究了如何处理它:\ begin {array} {| cc | c | cc |}
\ hline
lhs_ {old}和rhs_ {old}&res_i = lhs_ {old} \ land rhs_ {old}和lhs_ {new}&rhs_ {new} \\
\ hline
0& 0&0&0&0 \\
0&1&0&0&1 \\
1&0&0&1&0 \\
1&1&1&0 &0 \\
\ hline
\ end {array}
我们可以从此“真值表”中推断出\ $ rhs_ {old} \ land \ lnot lhs_ {old } = rhs_ {old} \ land \ lnot res_i \ $。如果我们将新发现重新注入代码中,则可以使用它完全摆脱
lhs_p_cpy
:Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_p_cpy = lhs_p;
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p_cpy & not rhs_p) ^ lhs_i;
rhs_p = (rhs_p & not lhs_p_cpy) ^ rhs_i;
通过这种方式,
res_i
也可以用于计算\ $ lhs_ {new} \ $,以使代码看起来更加对称(即使不会提高性能):Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p & not rhs_p) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
启用所有优化后,此代码虽然比以前的版本要快一点,但是仍然远不如将格雷码转换回常规整数,执行整数加法并将结果转换回格雷码的版本那样高效。嘿,如果不以各种可能的方式扭曲它,我们就无法优化这种算法!这就是为什么,我们还将尝试...
找到更好的最终条件
当前,该算法在结束之前循环遍历
res
中的每个位。可能可以更快地结束循环。让我们看一下一个较大的旧真值表,该表将用于其余的答案。为了简洁起见,我使用原始伪代码中的名称来命名列。我希望与我编写的代码的相似之处不要太难(\ $ A = lhs_i \ $,\ $ B = rhs_i \ $,\ $ E_ {old} = lhs_p \ $,\ $ F_ {old } = rhs_p \ $,\ $ S = A \ oplus B \ oplus res_i \ $,\ $ E_ {new} = lhs_p'\ $,\ $ F_ {new} = rhs_p'\ $)\ begin {array} {| cccc | cc | cc |}
\ hline
A&B和E_ {old}和F_ {old}和A \ oplus B&S和E_ {new }&F_ {new} \\
\ hline
0&0&0&0&0&0&0&0 \\
0&0&0&1&0&0 &0&1 \\
0&0&1&0&0&0&1&0 \\
0&0&1&1&1&0&1&0&0 \\
0&1&0&0&1&1&0&1 \\
0&1&0&1&1&1&0&0 \\
0&1&1& 0&1&1&1&1 \\
0&1&1&1&1&0&0&1 \\
\ hline
1&0&0&0& 1&1&1&0 \\
1&0&0&1&1&1&1&1 \\
1&0&1&0&1&1&0&0 \ \
1&0&1&1&1&0&1&0 \\
1&1&0&0&0&0&1&1 \\
1&1 &0&1&0&0&1&0 \\
1&1&1&0&0&0&0&0&1 \\
1&1&1&1&0&1 &1&1 \\
\ hline
\ end {array}
查看该表的第一行,我们可以看到,当\ $ A = B = E_ {old} = F_ {old} = 0 \ $时,我们得到\ $ S = E_ {new} = F_ {new} = 0 \ $,这意味着由于我们的结果从一开始就用\ $ A \ oplus B \ $填充,因此当满足上述条件时,我们可以停止循环(所有值等于\ $ 0 \ $ ),因为随后的位S始终为\ $ 0 \ $。一种简单的方法是在代码中右移“消耗”
lhs
和rhs
,以便它们最终达到零,并通过按位OR改变循环条件(在这里,为简单起见,仅for
循环): br /> Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = (lhs.value >> i) & 1u;
bool rhs_i = (rhs.value >> i) & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
虽然这可以降低算法的复杂性并极大地改善较小值的情况,但对于循环条件,
lhs | rhs | lhs_p | rhs_p
本身仍然是一堆操作。我们的目标是在所有这些变量均为\ $ 0 \ $时停止循环。我们可以做的一件事是在循环之前找到lhs
和rhs
之间的位具有最高的置位位和循环,直到该位被消耗为止。由于它们都以相同的速度被“消耗”,当消耗了设置位最高的一个时,则意味着另外一个已被消耗,因此我们只需要检查两者中最大的是否为\ $ 0即可。 \ $。通过简单地检查哪个具有最大的价值,我们可以轻松找到哪个具有最高的价值。这是经过修改的算法:for (Unsigned i{} ;
lhs | rhs | lhs_p | rhs_p ;
++i, lhs >>= 1u, rhs >>= 1u)
{
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = lhs.value & 1u;
bool rhs_i = rhs.value & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
}
更好地了解真值表
从我们构建的大真值表中,我们可以看到,当\ $ A = B = 0 \ $时,\ $ S = 1 \ $的唯一时间是\ $ E_ {old} = F_ {old} = 1 \ $。但是,当\ $ A = B = 0 \ $时,我们还可以看到\ $ E_ {new} \ $和\ $ F_ {new} \ $永远不能同时为\ $ 1 \ $。这意味着,一旦
lhs
和rhs
被消耗掉,且仅当res
为(lhs_p & rhs_p)
时,true
才能再改变一次。因此,我们可以从循环条件中提取lhs_p | rhs_p
部分,并在循环后精确地应用一个res
分配(我使用了while
循环,因为for
循环变得有点杂乱):template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
-> gray_code<Unsigned>
{
// Make sure lhs.value has the highest set bit
if (lhs.value < rhs.value)
{
std::swap(lhs.value, rhs.value);
}
// parity of lhs and rhs
bool lhs_p = is_odd(lhs);
bool rhs_p = is_odd(rhs);
gray_code<Unsigned> res = lhs ^ rhs;
for (Unsigned i{} ;
lhs | lhs_p | rhs_p ;
++i, lhs >>= 1u, rhs >>= 1u)
{
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = lhs.value & 1u;
bool rhs_i = rhs.value & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
}
return res;
}
有时候,更多的东西更少
当前,我们使用这样一个事实,即在某个时候
lhs
和rhs
最终将变为\ $ 0 \ $,并且在此之后只能进行一次修改这点。但是,我们目前不使用在某个点rhs
或lhs
是\ $ 0 \ $的事实。实际上,我们可以编写两个循环,而不是一个循环:一个循环运行直到lhs.value
和rhs.value
中的最小一个为\ $ 0 \ $,另一个循环运行直到另一个达到\ $ 0 \ $。第二个循环可以简化,因为我们知道一个rhs
(std::swap
中的最小值)在迭代过程中为\ $ 0 \ $:template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
-> gray_code<Unsigned>
{
// Make sure lhs.value has the highest set bit
if (lhs.value < rhs.value)
{
std::swap(lhs, rhs);
}
// parity of lhs and rhs
bool lhs_p = is_odd(lhs);
bool rhs_p = is_odd(rhs);
gray_code<Unsigned> res = lhs ^ rhs;
Unsigned i{};
while (lhs)
{
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = lhs.value & 1u;
bool rhs_i = rhs.value & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
++i;
lhs >>= 1u;
rhs >>= 1u;
}
// Last value in case lhs_p and rhs_p are 1
res ^= (lhs_p & rhs_p) << i;
return res;
}
重新思考逻辑
第二个循环可以重新思考为“当
res_i
为\ $ 0 \ $时会发生什么,而为\ $ 1 \ $时会发生什么”?在代码方面,应用了一些简化后,产生了以下循环:template<typename Unsigned>
auto operator+(gray_code<Unsigned> lhs, gray_code<Unsigned> rhs) noexcept
-> gray_code<Unsigned>
{
// Make sure lhs.value has the highest set bit
if (lhs.value < rhs.value)
{
std::swap(lhs, rhs);
}
// parity of lhs and rhs
bool lhs_p = is_odd(lhs);
bool rhs_p = is_odd(rhs);
gray_code<Unsigned> res = lhs ^ rhs;
Unsigned i{};
while (rhs)
{
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = lhs.value & 1u;
bool rhs_i = rhs.value & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = (rhs_p & not res_i) ^ rhs_i;
++i;
lhs >>= 1u;
rhs >>= 1u;
}
// We know that rhs is 0 now, let's get rid of it
while (lhs)
{
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
bool lhs_i = lhs.value & 1u;
lhs_p = (lhs_p & not res_i) ^ lhs_i;
rhs_p = rhs_p & not res_i;
++i;
lhs >>= 1u;
}
// Last value in case lhs_p and rhs_p are 1
res ^= (lhs_p & rhs_p) << i;
return res;
}
尽管此算法由于分支实际上通常比上一个算法慢,但它允许实现一些有趣的事情:分支之一始终将
rhs_p
设置为false
,并且在rhs_p == false
时无法执行。另一个分支不会更新rhs_p
,这意味着该循环可以被理解为“在rhs_p == true
时执行任务”,因此我们可以按如下方式重写原始的第二个循环(而不是新的转换后的第二个循环):while (lhs)
{
Unsigned res_i = lhs_p & rhs_p;
bool lhs_i = lhs.value & 1u;
if (res_i)
{
res ^= res_i << i;
lhs_p = lhs_i;
rhs_p = false;
}
else
{
lhs_p ^= lhs_i;
}
++i;
lhs >>= 1u;
}
已经应用了许多简化,因为我们知道在此循环中
rhs_p == true
。与第一个循环相比,我们最终在算法中有了第二个循环,这几乎是免费的,这意味着当我们将最高置位高的数字与最高置位小的数字相加时,其执行速度可能比以前的版本快得多。另请注意,由于最后一个循环在rhs_p == false
结束时结束,因此rhs_p & lhs_p
始终是false
是后循环后操作,因此我们可以完全删除它。res更新
lhs_p
,直到rhs_p
为false
为止。但是,rhs_p
设置为not lhs_p
,这意味着res
已更新为lhs_p
,而lhs_p
除最后一次迭代外为\ $ 0 \ $。换句话说,res
仅有意义地更新一次,因为当我们到达lhs_p == 1
时,我们在下一次迭代后立即离开循环。因此,我们可以调整循环以从中踢出res
更新:while (rhs_p)
{
res ^= lhs_p << i;
rhs_p = not lhs_p;
lhs_p = lhs.value & 1u;
++i;
lhs >>= 1u;
}
依次组合,这些优化为我们提供了以下算法:
if (rhs_p)
{
while (not lhs_p)
{
lhs_p = lhs.value & 1u;
++i;
lhs >>= 1u;
}
res ^= lhs_p << i;
}
虽然这是自从回答开始以来算法最通用和最优化的版本,但它仍然比将格雷码转换为规则整数,执行规则加法并将结果转换回格雷码慢一个数量级。不过,很高兴看到该解决方案可以推广和优化多少。我希望它可以为将来有兴趣添加两个格雷码的人们提供想法:)
#2 楼
我所有的评论都非常少:干代码:
您的构造函数/赋值运算符不是DRY。
constexpr explicit gray_code(value_type value) noexcept:
value( (value >> 1) ^ value )
{}
auto operator=(value_type other) & noexcept
-> gray_code&
{
value = (other >> 1) ^ other;
return *this;
}
如果发生变化,则必须在多个位置修改代码。您应该将工作包装在一个函数中。这两种方法都使用:
constexpr explicit gray_code(value_type value) noexcept:
value( convertValueToGray(value) )
{}
auto operator=(value_type other) & noexcept
-> gray_code&
{
value = convertValueToGray(other);
return *this;
}
新的返回类型:
不确定我是否喜欢新的返回样式:
我个人认为这不太清楚(但这可能是我,我需要习惯)。就我个人而言,我目前仅在派生返回类型时使用新样式。
auto operator=(value_type other) & noexcept
-> gray_code&
当不派生返回类型时,我仍然喜欢旧样式。
gray_code& operator=(value_type other) & noexcept
但这没什么大不了的。项目/公司样式指南将指示首选的内容。
公共成员变量:
您的值是公共的:
不确定这是否是有意的?您有分配在转入时进行转换的分配,还有一个转换运算符在转入时调整值。您是否真的要允许在未经监督的情况下访问原始值?
struct gray_code
{
value_type value;
成本转换
转换回
value_type
似乎是有点沉重:operator value_type() const noexcept
{
value_type res = value;
for (value_type mask = std::numeric_limits<value_type>::digits / 2
; mask ; mask >>= 1)
{
res ^= res >> mask;
}
return res;
}
如果这样做超过两次,可能会变得很昂贵。为什么不预先准备并将其存储在可变成员中。
// Variable containing the gray code
value_type value;
mutable value_type convertedValue; // Created on construction/assignment.
// Marked as mutable to indicate that
// it is not part of the state of the
// object but rather a cached value.
operator value_type() const noexcept {return convertedValue;}
我个人认为您的代码对无用的注释有点沉重。
评论
\ $ \ begingroup \ $
最后一个&是* this的ref限定词。它确保不能将运算符=与一个临时值一起使用,例如(a + b)= c。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:48
\ $ \ begingroup \ $
关于可变值,运算符++,-,>> =,<< =,&=,| =和^ =,因此存储第二个值并始终通过更改正确获取值可能会很昂贵。在此,用户有责任知道何时要将值转换回整数。我本可以明确地进行转换操作,以免发生随机转换。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:51
\ $ \ begingroup \ $
暴露价值是一种设计选择。对原始值进行处理时,许多位提示都可以更快,而快速是目标之一。因此,我认为公开类的内部没有问题:它们很有用,并且清楚地表明在用户的背后没有任何可疑的事情发生。
\ $ \ endgroup \ $
–莫文
2014年11月7日13:56
\ $ \ begingroup \ $
如果您故意让值成员处于开放状态而被位操作所困扰,那么可变成员技巧就不会有用。
\ $ \ endgroup \ $
–马丁·约克
2014年11月7日14:08
#3 楼
可以从主循环中删除一些操作(有些机器没有“ AndNot-instruction”):
// process bits from least significant to most in shorter operand
while (0 < rhs) {
Unsigned res_i = lhs_p & rhs_p;
res ^= res_i << i;
lhs_p ^= res_i ^ lhs;
rhs_p = (rhs_p ^ res_i ^ rhs) & 1;
++i;
lhs >>= 1u;
rhs >>= 1u;
}
// remaining bits of longer operand
if (0 == rhs_p)
return res;
lhs_p &= 1;
(而期望的运行长度用于均匀分布)位模式(1 +)小于1,)无需在循环中处理其余位:
if (0 != lhs_p)
return res ^= lhs_p << i;
Unsigned bit = lhs ^ (lhs & (lhs-1));
return res ^ (bit << (i+1));
我尝试将奇偶校验向左移动向右移动操作数:毫不奇怪,差异可以忽略不计。
Unsigned bit = 1;
for ( ; bit <= rhs && bit != 0 ; bit <<= 1) {
long both = lhs_p&rhs_p;
res ^= both;
lhs_p = (lhs_p ^ both ^ lhs) << 1;
rhs_p = ((rhs_p ^ both ^ rhs) & bit) << 1;
}
if (0 == rhs_p)
return res;
lhs_p &= bit;
if (0 != lhs_p)
return res ^= lhs_p;
bit = lhs ^ (lhs & (lhs-bit));
return res ^ (bit << 1);
确定后,就可以对种群计数和奇偶校验的位黑客变种进行编码,而无需过多考虑类型大小:
br />
评论
\ $ \ begingroup \ $
不是现代C ++的部件(有待改进)有历史-我今天才发现“我的” C ++环境接受C ++ 11-如果使用标记告知…
\ $ \ endgroup \ $
–灰胡子
16年11月14日在12:26
\ $ \ begingroup \ $
嗯,几天前我正在清理该代码,我想到确实有可能摆脱上一个循环。看起来我不必自己寻找解决方案,谢谢:)
\ $ \ endgroup \ $
–莫文
16-11-14在21:35
\ $ \ begingroup \ $
关于改进的奇偶校验算法,我前段时间写了一篇文章,逐步解释了这些技巧(仍然与格雷码相关)。不过,对于位技巧算法,我仍然更喜欢类型无关的解决方案。
\ $ \ endgroup \ $
–莫文
16年11月14日在21:36
\ $ \ begingroup \ $
(请注意,无循环可能不会产生明显的优势-可读性和速度方面都没有。)一个借口试图在“较短的操作数的最低至最高有效位”中“反转移位方向” -loop”(顺便说一句:在该变体中,其余位的处理看起来更好)是看是否可以在生成奇偶校验时反转移位方向(以最高/更高有效位为单位累加奇偶校验)来添加反映二进制编码整数的速度快于与位数成正比的速度。与bin2gray相比,可以改善混乱状态。
\ $ \ endgroup \ $
–灰胡子
16-11-14在23:03
评论
可能会遵循以下地址(格雷码基础和格雷码):扩展,应用和算术:底线是,可以进行超前进位的格雷[代码]加法器,但要付出可观的代价,并且无论采用哪种方式查看它,也是一个很大的性能损失。不是我们想要结尾的音符。