Stockfish score multiplication

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

User avatar
xr_a_y
Posts: 1871
Joined: Sat Nov 25, 2017 2:28 pm
Location: France

Stockfish score multiplication

Post by xr_a_y »

I'm struggling to understand this

Code: Select all

/// 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;
}
How can this handle multiplication by negative integer for example -1 ?
BeyondCritics
Posts: 396
Joined: Sat May 05, 2012 2:48 pm
Full name: Oliver Roese

Re: Stockfish score multiplication

Post by BeyondCritics »

I will specify the way it works in a more relaxed, mathematically manner, glossing over implementation issues. If you grok that, you should be able to solve the C++ problems on your own.
Let M=2^16, and define, as in C/C++:
uint16 := {0,...,M-1}
int16 := {-M/2,...,M/2-1}
uint32 := {0,...,M^2-1}
int32 := {-M^2/2,...,M^2/2-1}

Now assume you have two uint16 values x,y. You can define a function score_u(x,y) := xM+y.
score_u: uint16xuint16->uint32 is bijective (why?), so you can recover your original values.
Most importantly, you can now add scores modulo M^2 or multiply them by a constant modulo M^2 and then recover the original values at the end and that gives you the same result as if you would have worked with the individual scores modulo M individually, as long as there was never any "overflow" (why?).

And thinking of these numbers as members of residual classes, you are free to replace that number with every other member of his class you prefer at any moment and the result would still be correct up to the residual class.

Now consider the encoding of the score in Stockfish:

Code: Select all

constexpr Score make_score(int mg, int eg) {
  return Score((int)((unsigned int)eg << 16) + mg);
}
You must assume, that both operands are from int16, otherwise you get an "overflow". First both operands are converted to their corresponding element in uint32, then score_u is build and finally a conversion to the corresponding int32 is done.
Two's complement must be assumed.

Code: Select all

/// 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);
//...
  return result;
}
The representingint is fetched and multiplied with i. You could now convert that result to uint32 and from there invert score_u to get the individual values.
BeyondCritics
Posts: 396
Joined: Sat May 05, 2012 2:48 pm
Full name: Oliver Roese

Re: Stockfish score multiplication

Post by BeyondCritics »

Oops, there is still some error.
Once again let M=2^16

Code: Select all

constexpr Score make_score(int mg, int eg) {
  return Score((int)((unsigned int)eg << 16) + mg);
}
This first converts int16 values to their corresponding unsigned values modulo M, these are of course not uint16 values. Let us call that set SU.
Than you can still first ponder about a function Score_u: SU x SU -> unsigned, which is invertible and lets you interchange inversion and arithmetic.
Extend that to a function Score(x,y,) := int32(Score_u(unsigned(x),unsigned(y)), with x, y from int16. This function is invertible and let you interchange inversion and arithmetic also.
I hope i got it now.
elcabesa
Posts: 855
Joined: Sun May 23, 2010 1:32 pm

Re: Stockfish score multiplication

Post by elcabesa »

I'm just off topic, but if you like you can use an extension of gcc and clang

Code: Select all

using Score = int __attribute__ ((vector_size (16))); // a vector of int of size 16 bytes

Score a = {1,2,0,0};
Score b = {5,-6,0,0};

Score res = a + b;// {6,-4,0,0}
Score res2 = 2 * a; // {2,4,0,0} 
it will use simd instruction on x86 platform, it's portable ( if oyu accept to compile with gcc or clang). I don't know how much slower it's compared to stockfish code
User avatar
hgm
Posts: 27790
Joined: Fri Mar 10, 2006 10:06 am
Location: Amsterdam
Full name: H G Muller

Re: Stockfish score multiplication

Post by hgm »

In KingSlayer I use a slightly different method for interpolating eg and op scores. The game phase there runs from 0 (eg) to 24 (op). What I tabulate for the various eval terms (e.g. PST) is ((2*(op-eg)+1)/3 << 14) + eg. (The +1 if for causing rounding to closest in the integer devision; I will from now on cease to write it, and it should be understood that the division by 3 uses rounding rather than clipping.) I then multiply that by (1<<18) + phase. That would produce (2*(op-eg)/3 << 32) + (2*(op-eg)/3*phase << 14) + (eg << 18) + eg*phase. Because this is done in 32-bit arithmetic, the first term is entirely shifted 'out of view'. What is left I then shift 18 bits to the right. This gives

2*(op-eg)/3*phase/16 + eg + (eg*phase >> 18)

Assuming that the scores fit in 14 bits, including their sign bit, i.e. run from -4095 to +4095, and that extreme score of +40 will only occur when the game phase has already significantly dropped compared to its initial 24, the 3rd term will at most affect the rounding of the whole thing (and otherwise add or subtract an extra centi-Pawn at a score of +40). So we can neglect it, and have (perhaps with some additional rounding):

(op-eg)*phase/24 + eg

For phase=0 this is eg; for phase=24 this is (op-eg) + eg = op. If you analyze the rounding you will see that it is like (2/3)*(op-eg) has been stored with an accuracy of 4 bits behind the binary point, i.e. with a worst-case rounding error of 1/32, and in practice even 1/48 (as it is rounding because of a division by 3). So even after multiplying by the maximum phase, the error is at most +/-0.5, the best that can be expected.