【发布时间】:2022-11-22 10:56:02
【问题描述】:
Stockfish 国际象棋引擎需要存储残局得分和中局得分,以便进行评估。
它将它们打包成一个int,而不是单独存储它们。中局得分存储在低 16 位中。残局得分存储在较高的 16 位中,如果中局得分为正则按原样存储,如果为负则为负一。
这样做的好处是可以对两个数字并行进行运算(加法、减法、求反法和乘法)。
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
/// The least significant 16 bits are used to store the middlegame value and the
/// upper 16 bits are used to store the endgame value. We have to take care to
/// avoid left-shifting a signed int to avoid undefined behavior.
enum Score : int { SCORE_ZERO };
constexpr Score make_score(int mg, int eg) {
return Score((int)((unsigned int)eg << 16) + mg);
}
/// Extracting the signed lower and upper 16 bits is not so trivial because
/// according to the standard a simple cast to short is implementation defined
/// and so is a right shift of a signed integer.
inline Value eg_value(Score s) {
union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) };
return Value(eg.s);
}
inline Value mg_value(Score s) {
union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) };
return Value(mg.s);
}
#define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
constexpr T operator-(T d) { return T(-int(d)); } \
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
ENABLE_BASE_OPERATORS_ON(Score)
/// Only declared but not defined. We don't want to multiply two scores due to
/// a very high risk of overflow. So user should explicitly convert to integer.
Score operator*(Score, Score) = delete;
/// Division of a Score must be handled separately for each term
inline Score operator/(Score s, int i) {
return make_score(mg_value(s) / i, eg_value(s) / i);
}
/// Multiplication of a Score by an integer. We check for overflow in debug mode.
inline Score operator*(Score s, int i) {
Score result = Score(int(s) * i);
assert(eg_value(result) == (i * eg_value(s)));
assert(mg_value(result) == (i * mg_value(s)));
assert((i == 0) || (result / i) == s);
return result;
}
我了解加法、减法和求反法是如何工作的,但我难以理解的是乘法。将整数相乘如何正确地将残局和中局分数相乘?
【问题讨论】:
-
我不确定它是那样工作的。您可能需要在
uint64_t中进行一些洗牌? -
@tadman 嗯,它在一个非常流行的国际象棋引擎中,使用了很多年并且运行良好。此外,它在我测试的少数情况下也有效。
-
我的意思是在数学上,如果你将它们相乘,你会在末尾得到正确的位,但中间有高值和低值之间的交叉乘法,这可能是你不想要/不需要的。这里的代码似乎只描述乘以一个常量,我希望看到重复。
-
它没有:
Score operator*(Score, Score) = delete; -
为什么行不通?编码是米 + 2^16 电子.如果它乘以 k,那么你有k*m + 2^16 * (k * e).两个都米和电子乘以k因为乘法分布在加法之上。然而,正如断言所说,如果 k 太大,则米*米或者k*e可以溢出 16 位,这意味着结果无效。将两个分数相乘是行不通的。通过展开乘法很容易证明这一点。
标签: c++ bit-manipulation stockfish swar