1

First of all, I want to clarify that this question is different from the questions:

That this question is store and use, which mean I can do this

int64_t score = make_score(-15, 15);
score += make_score(-5, 5); //I can use (add, subtract) the score
int32_t a = get_a(score);
assert(a == -20); //-15 -5 = -20
int32_t b = get_b(score);
assert(b == 20);//15 + 5= 20

This is achievable for two 16-bit int in one 32-bit int (Stockfish did this):

/// 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);
}

I'm trying to upgrade mg and eg from int16_t to int32_t but I can't figure out how to do it, I always have trouble when ScoreA + ScoreB ruin the eg and mg inside the Score.

Here is what I tried and failed:

enum Score : int64_t { SCORE_ZERO };

constexpr Score make_score(int mg, int eg) {
  return Score((int)((uint64_t)eg << 32) + mg);
}

inline Value eg_value(Score s) {
  union { uint32_t u; int32_t s; } eg = { uint32_t(unsigned(s + 0x80000000) >> 32) };
  return Value(eg.s);
}

inline Value mg_value(Score s) {
  union { uint32_t u; int32_t s; } mg = { uint32_t(unsigned(s)) };
  return Value(mg.s);
}
ChessLover
  • 11
  • 1
  • If you need to pack to `int32_t` together, why don't you use a `class` or `struct`? And why do you abuse enums as integers? `Score` should better be an alias for `int64_t` instead of an enum. – Lukas-T Jul 10 '20 at 05:01
  • Expanding on the previous comment, where and how do you use `score` as an `int64_t` and why couldn't you use a `class`, there? – Bob__ Jul 10 '20 at 06:49
  • @churill : I'll definitely use class for Score (and I'm not use enums as integers, Stockfish devs did that :D), but I'm trying to update old code, I migrate to class will generate more trouble for me :D – ChessLover Jul 10 '20 at 09:00
  • PS: depressed, I decided to migrate Score to struct to save me from the bitwise madness, strangely, I success! turn out migrate to struct is easier. – ChessLover Jul 10 '20 at 10:33

5 Answers5

6

Use memcpy.

As the comment in the original solution pointed out, this kind of bit manipulations are a minefield of potential undefined behavior. memcpy allows you to get rid of those and is well understood by modern compilers, so it will still result in efficient machine code.

enum Score : int64_t { SCORE_ZERO };

enum Value : int32_t { FORTYTWO };

inline Score make_score(int32_t mg, int32_t eg) {
    int64_t combined;
    std::memcpy(&combined, &eg, 4);
    std::memcpy(reinterpret_cast<char*>(&combined) + 4, &mg, 4);
    return Score(combined);
}

inline Value eg_value(Score s) {
    int32_t eg;
    std::memcpy(&eg, &s, 4);
    return Value(eg);
}

inline Value mg_value(Score s) {
    int32_t mg;
    std::memcpy(&mg, reinterpret_cast<char*>(&s) + 4, 4);
    return Value(mg);
}

Try it on godbolt.

ComicSansMS
  • 51,484
  • 14
  • 155
  • 166
  • 1
    The correct answer imho. Few suggestions: `make_score` should be inline as well otherwise linker will complain about duplicate symbols. Also why the `reinterpret_cast`s? The parameters of `memcpy` are `void*` and every address is implictly convertible to it. Lastly instead of hardcoding `magical 4` I would recommend use of `sizeof`. – Resurrection Jul 10 '20 at 06:58
  • There is bug: `mg_value((Score)(make_score(0,0)-make_score(15,15)))` should return `-15`, but it return `-16` – ChessLover Jul 10 '20 at 09:38
  • @Resurrection You need the cast here to get the offset into the middle of the `int64_t`. With the original `Score*` pointer value you can only access addresses aligned to `sizeof(Score)`. Of course casting to `void*` instead would also work, but many compilers warn about pointer arithmetic on `void*`. Good catch about the missing `inline`. – ComicSansMS Jul 10 '20 at 11:22
1

The problem is that you still have some "int" and "unsigned" keywords left that still convert into the 32 bit version. So replace each "int" with "int64_t" and each "unsigned" with "uint64_t" and it should work as expected.

koalo
  • 2,113
  • 20
  • 31
0

This can be different approach for this question

#include<iostream>
#include<cstdint>
#include<bitset>
using namespace std;
int main()
{
    bitset<32> bit32[2] ={ 45 ,-415152545 };
    bitset<64> bit64;
    
    // store in 64 bit varibale
    int index=0;
    int position=0;
    for(int i=0;i<64;i++)
    {
       bit64[i] =bit32[index][i-position];
       if(i==31)
         {   index = 1; position=32; }
    }
    
    // reset 32 bit container ,index and position 
       bit32[2] ={0};
       index=0;
       position=0;
    
    
   // fetching data in 32 bit container from 64 bit and assign it into a and b .
     int32_t a;
     int32_t b;
    for(int i=0;i<64;i++)
    {
       bit32[index][i-position] = bit64[i];
       if(i==31)
         {   index = 1; position=32; }
    }
    
  
    a = bit32[0].to_ulong();
    b = bit32[1].to_ulong();
    cout<<a<<" "<<b;
 
}
Ramesh Choudhary
  • 366
  • 4
  • 11
0

You could use union as well:

#include <stdint.h>
#include <iostream>


union Score {
    int64_t    u64;
    int32_t    u32[2];

    Score() : u64(0) {}
    Score(int64_t v) : u64(v) {}
    Score(int32_t a, int32_t b): u32{a, b} {}

    Score & operator=(Score const & original) { if(&original != this) { u64 = original.u64; } return *this; }

    int32_t get_a() {return u32[0];}
    int32_t get_b() {return u32[1];}
    int64_t get() {return u64;}

    Score operator+(Score const & other) {
            return Score(u32[0] + other.u32[0], u32[1] + other.u32[1]);
        }

    Score & operator+=(Score const & other) {
            u32[0] += other.u32[0];
            u32[1] += other.u32[1];
            return *this;
        }
};


int main() {

    Score v(-15, 15);

    std::cout << "The size is: " << sizeof(Score) << " Bytes" << std::endl;
    std::cout << "A is: " << v.get_a() << std::endl;
    std::cout << "B is: " << v.get_b() << std::endl;

    std::cout << "adding -5, +5" << std::endl;
    Score v1 = v + Score(-5, 5);
    std::cout << "A is: " << v1.get_a() << std::endl;
    std::cout << "B is: " << v1.get_b() << std::endl;

    std::cout << "adding -10, +10" << std::endl;
    v += Score(-10, 10);
    std::cout << "A is: " << v.get_a() << std::endl;
    std::cout << "B is: " << v.get_b() << std::endl;


    return 0;
}

Output:

The size is: 8 Bytes
A is: -15
B is: 15
adding -5, +5
A is: -20
B is: 20
adding -10, +10
A is: -25
B is: 25
PirklW
  • 484
  • 2
  • 16
0

It's easy.

int64_t value;
int32_t* value1 = (int32_t*)&value;
int32_t* value2 = (int32_t*)&value + 1;

Example:

#include <cstdint>


int main() {
    int64_t value;
    int32_t* value1 = (int32_t*)&value;
    int32_t* value2 = (int32_t*)&value + 1;

    *value1 = 10; // Example
    *value2 = 20;

    return 0;
}
Ihor Drachuk
  • 1,265
  • 7
  • 17