0

I have here one small "PLC", which run on a 32bit MCU. Biggest variable which I can define is uint32 (unsigned int). I'm reading one meter which gives me 48bit value in 6 chars (through serial line). I have now this value stored in two 32bits by shifting the chars.

T1Low = (unsigned int) mybuffer[3] << 24 | (unsigned int) mybuffer[2] << 16 |
        (unsigned int) mybuffer[1] <<  8 | (unsigned int) mybuffer[0];
T1High = (unsigned int) mybuffer[5] << 8 | (unsigned int) mybuffer[4];

But I would like to divide the 48bit value by 1000 and save it to one 32bit (because after the divide, it will give me enough precision).

In theory something like

unsigned int result = (T1High << 32 | T1Low) / 1000;

Of course this doesn't work because of too big shift... Is there any way how to do this?

EDIT: description of the resolution: The meter measure in kWh, but the value is given in Wh. Maximum value is 9 999 999, which is enough to save in 32bit. But from the meter I will get 9 999 999 000... So I need to cut the 3 zeros at the end..

EDIT2: Right answer is given by cmaster :) If someone want to put the code into only 2 lines, then:

unsigned int value1;
unsigned int value2;
unsigned int value3;

value2 += (value1%1000) << 16;
unsigned long result = ((value2 / 1000) << 16) | ((value3 + ((value2 % 1000) << 16)) / 1000);
Daniel
  • 58
  • 6
  • 1
    You'll need to look up 'multi-precision arithmetic' or thereabouts. Yes, there are ways to do it. The full general purpose solutions ([GMP](https://gmplib.org/) — GNU Multi-Precision — for example) may be overkill, but you'll end up using functions to do the job and they won't be trivial. – Jonathan Leffler Apr 06 '17 at 14:16
  • unsigned int result = (T1High << 22 | T1Low >> 10) – KonstantinL Apr 06 '17 at 14:21
  • http://stackoverflow.com/questions/5284898/implement-division-with-bit-wise-operator – aicastell Apr 06 '17 at 14:23
  • KonstantinL: I will lost precision too much. I just need to cut the 3 zeros from the number... – Daniel Apr 07 '17 at 13:44

3 Answers3

3

Mathematically speaking, T1High << 32 | T1Low is equivalent to

T1High * 2**32 + T1Low

and dvision of sum can be replaced with sum of division which can bring us to the following formula:

((T1High * 2**16 * 2**16) + T1Low) / 1000
(T1High * 2**16 / 1000 * 2**16) + (T1Low / 1000)
(((T1High << 16) / 1000) << 16) + (T1Low / 1000)

It may cause losing some precision, though:

4823248397 = ((1123 << 32) + 123899) / 1000
4823187579 = (((1123 << 16) / 1000) << 16) + (123899 /1000)
myaut
  • 11,174
  • 2
  • 30
  • 62
2

I'd do a division in base 2^16: Split your value into three 16 bit chunks, store those in uint32_t variables, then do the following:

uint32_t result1 = value1/1000;
uint32_t remainder = value1 - result1*1000;
value2 += remainder << 16;
uint32_t result2 = value2/1000;
remainder = value2 - result2*1000;
value3 += remainder << 16;
uint32_t result3 = value3/1000;

Of course, you could also formulate this as a loop, but I doubt that it's worth it. This method is as precise as it can be. The drawback is, that it needs three divisions and two multiplications, which may cause performance issues if your computing resources are tight.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • I tried this methode. I tried it on max number 9 999 999 000, from which I want 9 999 999. So I splited the number to 3 16bit (0xE018, 0x540B, 0x0002). I get 0x00, 0x98, 0x7f – Daniel Apr 07 '17 at 14:53
  • @Daniel Seems you are using the wrong byte order: `0x0002` should be `value1` and `0xe018` should be `value3` – cmaster - reinstate monica Apr 07 '17 at 14:56
  • @Daniel Just think how you would divide with pen and paper. – cmaster - reinstate monica Apr 07 '17 at 14:58
  • Thanks for reply. I tried to switch the order too, I got 0x39, 0x5E4A, 0xC353. EDIT: It looks like I made it right, but I stored it bad. When now I tried it on repl.it It gives me right: 0x0, 0x98 and 0x967f :) I will test it again on Monday, I will be out. – Daniel Apr 07 '17 at 18:55
  • Also there is possible small optimization: Use modulo instead of "manual" calculation of remainder and in theory I don't need to store the first result :) `value2 += (value1%1000) << 16; unsigned long result2 = value2/1000; value3 += (value2%1000) << 16; unsigned long result3 = value3/1000;` – Daniel Apr 07 '17 at 19:22
  • @Daniel That "optimization" most likely won't buy you a single cycle: There is no assembler command for modulo on any architecture I've seen. As such, the modulo is translated to a division followed by a multiplication, just as the code I wrote puts it. As long as `value1` is a local variable which may be allocated to a register, I doubt that the resulting assembly will show any differences. – cmaster - reinstate monica Apr 07 '17 at 20:58
2

But I would like to divide the 48bit value by 1000 and save it to one 32bit

Your spec doesn't make sense. 2^48 = 2.81e14 and 2.81e14 / 1000 = 2.81e11. A 32 bit number can hold 4.29e9 so dividing by 1000 doesn't solve anything - the number won't fit.

That being said, don't divide by 1000, divide by 1024. Which is the same as right-shifting the number by 10. Or if you will, discard the 10 least significant bits.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I know it sound weird, but the reason is simple: The meter measure in kWh, but the value is given in Wh. Maximum value is 9 999 999, which is enough to save in 32bit. But from the meter I will get 9 999 999 000... So I need to cut the 3 zeros at the end... – Daniel Apr 07 '17 at 13:15