https://github.com/syzygy1/Stockfish/co ... e_no_unionkbhearn wrote:Knew i shouldn't have tried extracting extraneous brackets... one of them was needed, corrected below:
Code: Select all
typedef uint32_t Score; inline Score make_score(unsigned int mg, unsigned int eg) { // implicitly converting signed inputs return (eg << 16) + mg; } // mask and manual sign extension inline Value mg_Value(Score s) { static const uint32_t mask = 0xFFFFU; static const int sign = 0x8000; return ((int)(s & mask) ^ sign) - sign; } // if lower 16 bits are negative add 0x8000 to propagate up the borrowed 1 from upper 16 bits, then shift and sign extend inline Value eg_Value(Score s) { static const uint32_t borrow = 0x8000U; static const int sign = 0x8000; return ((int)((s + borrow) >> 16) ^ sign) - sign; }
I think Score has to remain an enum for the division operator overload to work (not 100% sure). But that should not have any impact.
I measure 0.15% slow down, which, even if reliable, is well within normal compiler noise. For other or older compilers it might be different.
Of course the most important thing is to get addition/subtraction (and multiplication by an integer) fast. Extraction of eg and mg is a far less common operation.
Regarding readability... I'm not so sure manually handling the sign is "more readable" than a series of casts. But whatever solution ones uses to wrap two signed 16-bit ints into one 32-bit int, it is going to involve a bit of arithmetic trickery. It's not as easy as "just use clean and simple shift instructions and you are done". (However, it is certainly better than the solution shown in the opening post, if only because it does not depend on endianness.)
It's still not fully clear to me whether SF's current implementation gives undefined behaviour according to the C++11 standard. But it is probably guaranteed to work on the major compilers.
It seems that whatever int-wrapping solution one chooses, there will be some (very mild) reliance on implementation-defined behaviour.