Suppose there are some values ​​about which it is known only that they are <= 10^6 . Taking this fact into account, we take an array with int and store it. But what if you don’t want to use extra bytes (let sizeof(int) == 4 ) of type int - in this case, you can do with 3 bytes, since 10^6 < 2^24 . How to implement this ??

  • In this world, people do not manufacture processors with 20 bit registers. Although you can try packing 3 values ​​with bit shifts and a mask into one 64 bit register. But there still remains 4 bits extra, which will slow down the entire system. - igumnov
  • So get a char * array, take 3 bytes and bring them to int - Alexey Sarovsky
  • Although in fact there is another sophisticated version in which everything is aligned. You can store the first high-order 16 bits of the first four numbers in the 64-bit register and the remaining 4-bits of four numbers in the 16-bit one. But this all means a strong headache with packing and unpacking. - igumnov
  • @AlekseySarovsky and can you learn more about "take 3 bytes" ?? - ampawd

2 answers 2

Storage of N 24 bit integers can be organized in the banal array unsigned char data[N * 3] (meaning 8-bit unsigned char ).

Reading a number with index i from an array is done simply as

 unsigned value = 0; memcpy(&value, &data[i * 3], 3); 

and the record is like

 memcpy(&data[i * 3], &value, 3); 

In order to support signed numbers in the format of 2's-compliment, if necessary, you will have to make another extension of the sign when reading and that's it. (Ie, multiply the most significant bit of the 3rd byte of importance for the whole of the most significant byte of value.)

The above copies are specific to little-endian platforms, and will be slightly different for big-endian platforms.

  • will work only for little-endian machines (eg with percents of Intel), but not for big-endian (eg SPARC), so for portability it is better to use something like value = (((int) data [i * 3 + 2]) << 16) | (((int) data [i * 3 + 1]) << 8) | ((int) data [i * 3]) - ipx

Well, for example, you store three bytes per index and convert the indices themselves.

 struct Int24Container { int8_t* payload; size_t size; }; int24c_init(Int24Container* cont, size_t size) { cont->payload = (int8_t*)malloc(3 * size); cont->size = size; } int24c_destroy(Int24Container* cont) { delete cont->payload; } int32_t in24c_get(Int24Container* cont, size_t idx) { return cont -> payload[idx] | (cont -> payload[idx + 1] << 8) | (cont -> payload[idx + 2] << 16); } void in24c_set(Int24Container* cont, size_t idx, int32_t x) { cont -> payload[idx] = x & 0xff; cont -> payload[idx + 1] = (x >> 8) & 0xff; cont -> payload[idx + 2] = (x >> 16) & 0xff; } 

If you want in C ++ style, you can do it the way vector<bool> does.

 class Int24Container { vector<int8_t> payload; public: Int24Container(size_t size) : payload(size * 3) { } class reference { Int24Container& cont; size_t idx; public: reference(Int24Container& cont, size_t idx) : cont(cont), idx(idx) {} reference(const reference& ref) : cont(ref.cont), idx(ref.idx) {} reference& operator = (int x) { cont.payload[idx] = x & 0xff; cont.payload[idx + 1] = (x >> 8) & 0xff; cont.payload[idx + 2] = (x >> 16) & 0xff; return *this; } reference& operator = (const reference& ref) { cont.payload[idx] = ref.cont.payload[idx]; cont.payload[idx + 1] = ref.cont.payload[idx + 1]; cont.payload[idx + 2] = ref.cont.payload[idx + 2]; return *this; } operator int() const { return cont.payload[idx] | (cont.payload[idx + 1] << 8) | (cont.payload[idx + 2] << 16); } }; reference operator[] (int index) { return reference(*this, index * 3); } }; 

Instead of byte-wise copying, you should try memcpy , which may be faster.