Centipawns and Millipawns

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Centipawns and Millipawns

Post by bob »

Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Centipawns and Millipawns

Post by bob »

BubbaTough wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
I tried variations of that, and for me...even with a scale of 1/1000...it hurt performance.

-Sam
I think the conclusion for that is that your values are probably reasonably tuned. I have seen some programs where every score is an even number, for example, so that the rightmost bit is never 1. They are using 1/50th of a pawn without thinking about it....

I switched from millipawns to centipawns way back in Crafty. I started with millipawns because that is what we used in Blitz/CrayBlitz since the early days. I found centipawns resulted in less arbitrary scores (is this a .01, or a .011 or a .009 score)? My tests with .1 were worse, but now I can quantify exactly how much worse on the cluster and am going to run this test hopefully tomorrow.
Edsel Apostol
Posts: 803
Joined: Mon Jul 17, 2006 5:53 am
Full name: Edsel Apostol

Re: Centipawns and Millipawns

Post by Edsel Apostol »

BubbaTough wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
I tried variations of that, and for me...even with a scale of 1/1000...it hurt performance.

-Sam
I can't edit my post anymore. In my last sentence I mean too fine instead of too coarse for the search.

I think the best value for the basic unit is somewhere between 0.01 and 0.05. Stockfish uses 0.03125.

There are some exceptions though. Take for example Strelka that uses 3399 as Pawn Value. Its basic uinit is 0.0003.

In the end, it all boils down to who's eval function is well tuned may it be using 1/1000 or 1/100.
Edsel Apostol
Posts: 803
Joined: Mon Jul 17, 2006 5:53 am
Full name: Edsel Apostol

Re: Centipawns and Millipawns

Post by Edsel Apostol »

bob wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
I am not sure if zero is a legitimate shift, I don't have much exposure to hardware other than the PC. I assume that since it's zero the hardware will be intelligent enough not to shift anything. Thanks for pointing it out.
Dann Corbit
Posts: 12537
Joined: Wed Mar 08, 2006 8:57 pm
Location: Redmond, WA USA

Re: Centipawns and Millipawns

Post by Dann Corbit »

Edsel Apostol wrote:
bob wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
I am not sure if zero is a legitimate shift, I don't have much exposure to hardware other than the PC. I assume that since it's zero the hardware will be intelligent enough not to shift anything. Thanks for pointing it out.
Shifts of zero bits are defined. Here is what the current C standard says:

6.5.7 Bitwise shift operators
Syntax
1 shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
Constraints
2 Each of the operands shall have integer type.
Semantics
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
Edsel Apostol
Posts: 803
Joined: Mon Jul 17, 2006 5:53 am
Full name: Edsel Apostol

Re: Centipawns and Millipawns

Post by Edsel Apostol »

Dann Corbit wrote:
Edsel Apostol wrote:
bob wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
I am not sure if zero is a legitimate shift, I don't have much exposure to hardware other than the PC. I assume that since it's zero the hardware will be intelligent enough not to shift anything. Thanks for pointing it out.
Shifts of zero bits are defined. Here is what the current C standard says:

6.5.7 Bitwise shift operators
Syntax
1 shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
Constraints
2 Each of the operands shall have integer type.
Semantics
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
Based on this:
If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
When E2 = 0 then:

Code: Select all

E1 * 2^0 = E1 * 1 = E1
Is the above assumption correct? Is 2^0 undefined also?
Uri Blass
Posts: 10267
Joined: Thu Mar 09, 2006 12:37 am
Location: Tel-Aviv Israel

Re: Centipawns and Millipawns

Post by Uri Blass »

bob wrote:
rjgibert wrote:I can't imagine what a centipawn means. What is the reason for using such a fine grained evaluation unit? Is there really any benefit? I understand mate-in-N evaluations and maybe what a decipawn means, but that is as far as I can go with my 2234 rating.

The only other thing I can relate to is a judgement call that position X appears to be better than position Y even though I might quantify them as having the same evaluation.

So my question is, do centipawns evaluations really help chess engines and why? What would you lose by using say 1/20th of a pawn as the finest grain for your evaluations as opposed to the finer grained evaluations? It appears to me that chess programmers are doing something akin to measuring something to the nearest hundredths, while having a standard deviation of as much as a quarter of a pawn.

In engineering, 1.234 +/- 0.1 is just silly. Why isn't it just as silly in computer chess?

And then there even are engines that use millipawns. What's that about?
Having done all three, my take on the issue is this:

(1) decipawns (0.1) is too coarse. Not every positional consideration is worth 0.1 pawns, so you either have to round the score up to 0.1, or else throw it out since it would be zero.

(2) millipawns (0.001) is too fine. I do not believe that my evaluation has any .001 accuracy ideas in it. As you spread the evaluation scores out, your tree search becomes less efficient (for example, compare a program with no positional scoring to one with, with respect to tree size).

(3) centipawns (0.01) is reasonable. One could make the argument that maybe .05 is better (1/20th of a pawn). Or some other number. But my intuition after trying all three during the development of Crafty is that the right value lies in the interval {0.01, 0.1}. Whether it is on one end or the other, or somewhere in the middle, is a point for conjecture. It is too hard to test the idea, although I suppose I could just do a normal eval and then at the end, reduce it to the accuracy needed. But it is not easy to test for smaller than .01 increments since no increments in crafty would be smaller than .01...
You do not need values that are smaller than 0.01 to have millipawns evaluation.

If you do an average between opening evaluation and endgame evaluation when the weight of the opening evaluation is based on the stage of the game then the difference between evaluations can be less than 0.01 pawns.

Uri
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Centipawns and Millipawns

Post by bob »

Edsel Apostol wrote:
Dann Corbit wrote:
Edsel Apostol wrote:
bob wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
I am not sure if zero is a legitimate shift, I don't have much exposure to hardware other than the PC. I assume that since it's zero the hardware will be intelligent enough not to shift anything. Thanks for pointing it out.
Shifts of zero bits are defined. Here is what the current C standard says:

6.5.7 Bitwise shift operators
Syntax
1 shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
Constraints
2 Each of the operands shall have integer type.
Semantics
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
Based on this:
If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
When E2 = 0 then:

Code: Select all

E1 * 2^0 = E1 * 1 = E1
Is the above assumption correct? Is 2^0 undefined also?
anything^0 = 1.
Edsel Apostol
Posts: 803
Joined: Mon Jul 17, 2006 5:53 am
Full name: Edsel Apostol

Re: Centipawns and Millipawns

Post by Edsel Apostol »

bob wrote:
Edsel Apostol wrote:
Dann Corbit wrote:
Edsel Apostol wrote:
bob wrote:
Edsel Apostol wrote:I have tried this idea just recently. I'm still using my old scoring of centipawns but at the end of the eval I use something like:

Code: Select all

return score &= ~(&#40;1<<grain&#41;-1&#41;;
where grain is a number 0 to 4.

Grain with 0 value means no change in the score, 1 will make it round to 2, 2 to 4 and 3 to 8, and 4 to 16.

I only have tried grain values 0, 2, 3. 0 still gives the best result, though my number of games is only 1200 each and the difference in elo between 0 and 2,3 is only 10. I will test with 1 to see if it performs better.

Using eval grain in Stockfish works because it uses 256 as Pawn value and it is too coarse for the search so it scales it by 4, meaning the finest unit for its search is 1/64. I think this is the optimal value.
Is zero a legit shift amount? lots of hardware will handle that in unexpected ways. Some will not shift at all, some will shift as if that were the word size, which means a value of zero will result...
I am not sure if zero is a legitimate shift, I don't have much exposure to hardware other than the PC. I assume that since it's zero the hardware will be intelligent enough not to shift anything. Thanks for pointing it out.
Shifts of zero bits are defined. Here is what the current C standard says:

6.5.7 Bitwise shift operators
Syntax
1 shift-expression:
additive-expression
shift-expression << additive-expression
shift-expression >> additive-expression
Constraints
2 Each of the operands shall have integer type.
Semantics
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand. If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.
4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
Based on this:
If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
When E2 = 0 then:

Code: Select all

E1 * 2^0 = E1 * 1 = E1
Is the above assumption correct? Is 2^0 undefined also?
anything^0 = 1.
What I mean if that is still undefined in the C standard. I already knew that anything^0 = 1 in my algebra class since I'm 13 years old.

If one we're to base on this deductions, if anything^0=1 is defined in the C standard, therefore a shift by 0 will still produce the same number being shifted. So there is no undefined behavior in it. If there will be problems with other hardware concerning this, it must be the problem of the hardware itself and not in the code.
wgarvin
Posts: 838
Joined: Thu Jul 05, 2007 5:03 pm
Location: British Columbia, Canada

Re: Centipawns and Millipawns

Post by wgarvin »

Edsel Apostol wrote: What I mean if that is still undefined in the C standard. I already knew that anything^0 = 1 in my algebra class since I'm 13 years old.

If one we're to base on this deductions, if anything^0=1 is defined in the C standard, therefore a shift by 0 will still produce the same number being shifted. So there is no undefined behavior in it. If there will be problems with other hardware concerning this, it must be the problem of the hardware itself and not in the code.
The authors of the C standard wrote: 4 The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 * 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 * 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
5 The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 / 2E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.
They were pretty careful with the definitions. They standardized everything that was predictable across real hardware (of that era) and avoided specifying behaviours for things that have different behaviour on different hardware (shifting by greater or equal to the number of bits in a word, 1s- and 2s-complement machines, etc.)

"The result of E1 << E2 is E1 left-shifted E2 bit positions". If E2 is zero, it is left-shifted by zero bit positions, and there are no vacated bits to fill.

"The result of E1 >> E2 is E1 right-shifted E2 bit positions." Same thing, except that it is implementation-defined if E1 has a signed type and a negative value. Which means it has to do something consistent for your implementation, but the standard does not specify what. In practice, every implementation you'll ever come across leaves the value of E1 unchanged if E2 is zero (because it is what the underlying shift and rotate instructions do, at least on every platform I've ever heard of).

When this standard was written, they had to contend with 1s-complement math and byte- and word-sizes that were not multiples of 8 bits. We are spoiled nowadays: Every machine made in at least the last 20 years has IEEE floats, 2's complement integers, 8-bits to a byte, and 16 or 32 or 64 bits to a word.