0

Can we use bitwise operator for conversion from decimal to other bases other than 4, 8, 16 and so on? I understand how to do that for 4, 8, 16 and so on. But for conversion from decimal to base 3, or base 12, for example, I don't know. It is possible?

Cezar
  • 3
  • 2
  • 2
    Not as easily. Converting to bases requires calculating division and remainders. For bases that are a power of 2, these correspond directly to bitwise operators: right shift == divide by 2. For other bases, there's no direct correspondence. – Barmar Jun 08 '22 at 16:36
  • No sure what you mean by bitwise operators, you only need `/` and `%`. – Fiddling Bits Jun 08 '22 at 16:36
  • 1
    of course. Many small embedded MCUs/CPUs don't have division/multiplication at all and you have to do multiplication/division with bitwise operators. It's not efficient though, obviously. The smallest Turing-complete machine [has only a single instruction](https://stackoverflow.com/a/19677755/995714) and they can do everything any other Turing-complete machines can do – phuclv Jun 08 '22 at 16:41

1 Answers1

1

I assume in your question you mend conversion from binary to other bases.

All arithmetic operations can be reduced to bitwise operations and shifts. That's what the CPU is doing internally in hardware too.

a + b ==> (a ^ b) + ((a & b) << 1)

The right side still has a + in there so you have to apply the same transformation again and again till you have a left shift larger than the width of your integer type. Or do it bit by bit in a loop.

With two's-complement:

-a ==> ~a + 1

And if you have + and negate you have -. * is just a bunch of shifts and adds. / is a bunch of shifts and subtract. Just consider how you did multiplication and long division in school and bring that down to base 2.

For most bases doing the math with bitwise operations is insane. Especially if you derive your code from the basic operations above. The CPUs add, sub and mul operations are just fine and way faster. But if you want to implement printf() for a freestanding environment (like a kernel) you might need to do a division of uint64_t / 10 that your CPU can't do in hardware. The compiler (gcc, clang) also isn't smart enough do this well and falls back to a general iterative uint64_t / uint64_t long division algorithm.

But a division can be done by multiplying by the inverse shifted a few bits and the shifting the result back. This method works out really well for a division by 10 and you get nicely optimized code:

uint64_t divu10(uint64_t n) {
    uint64_t q, r;
    q = (n >> 1) + (n >> 2);
    q = q + (q >> 4);
    q = q + (q >> 8);
    q = q + (q >> 16);
    q = q + (q >> 32);
    q = q >> 3;
    r = n - (((q << 2) + q) << 1);
    return q + (r > 9);
}

That's is shorter and faster by a magnitude or two to the general uint64_t / uint64_t long division function that gcc / clang will call when you write x / 10.

Note: (((q << 2) + q) << 1) is q * 10. Another bitwise operation that is faster than q * 10 when the cpu doesn't have 64bit integers.

Goswin von Brederlow
  • 11,875
  • 2
  • 24
  • 42
  • 1
    Gcc and clang do not use a DIV opcode for division by small constants (or even not so small constants). TIAS. – rici Jun 08 '22 at 21:45
  • @rici gcc (before 11) and clang do a function call on ARM. gcc >= 11 finally multiplies by an inverse constant on both 32bit and 64bit systems across archs. Even with the multiplication code I think the bit shifting above is better for 32bit if you reorder it a bit. – Goswin von Brederlow Jun 08 '22 at 22:33
  • Sure, optimised division is not available for every platform. But your answer makes it sound like GCC and Clang *never* do it, which is simply not true. To me, it feels like FUD, which is why I felt compelled to counter. – rici Jun 09 '22 at 02:29
  • @rici clang still never does it and for gcc the fix is recent. Maybe it was unclear that I was only talking about integer types larger than the CPUs register size? – Goswin von Brederlow Jun 09 '22 at 08:49
  • Yes, I think you could make that much clearer. As well as the fact that your reference to freestanding printf implementations is because existing standard C libraries already optimise division by 10, even for wide integer types. – rici Jun 09 '22 at 16:07
  • @rici standard C libraries have nothing to do with divison by 10. That's something the compiler does. – Goswin von Brederlow Jun 09 '22 at 20:51
  • I quote your answer: *if you want to implement printf() for a freestanding environment (like a kernel) you might need to do a division of uint64_t / 10 that your CPU can't do in hardware*. I don't know what difference the freestanding environment makes to the existence of division, other than the fact that you might not have an implementation of `printf` available; a standards-compliant freestanding environment has to include whatever support functions the generated code might require. But the coders who implemented printf in the standard libraries mostly had the same concerns you do about... – rici Jun 09 '22 at 22:29
  • ... the efficiency of the generated code's divide by 10 for wide types such as `size_t`. So if you look around, you'll find hand-optimised divide-by-small-constants code in the implementation of `printf`. (In glibc, it's hidden inside the non-standard `itoa` function, which needs to deal with bases from 2 to 36. Their printf calls the internal implementation of itoa, which includes a table-driven divide-by-multiply function.) – rici Jun 09 '22 at 22:32
  • @rici The point of freestanding is that you have to reinvent the wheel. Only reason you would need to this stuff over the ready made standard and support libraries C otherwise has. – Goswin von Brederlow Jun 09 '22 at 22:33
  • Ok, but your suggestion was to hand-optimise divide-by-10. So obviously the function has to do with division by 10. – rici Jun 09 '22 at 22:34
  • I'm not suggesting they didn't do it. I'm saying that if you don't already have glibc you might need to do it yourself. And that the compiler doesn't do it for you. At least gcc didn't before 11 and clang still doesn't. – Goswin von Brederlow Jun 09 '22 at 22:35
  • The functions GCC uses to do long division are not part of glibc. They're in libgcc, which (unlike glibc) is part of GCC. – rici Jun 09 '22 at 22:37
  • and I never said any different. They are just slow. – Goswin von Brederlow Jun 09 '22 at 23:37
  • They might be slow but they are present even in a free-standing environment, contrary to your claim that "if you don't already have glibc, you might need to do it yourself." Certainly, you might want to do it yourself on certain platforms, but there are resources (such as freely available itoa implementations and libdivide). – rici Jun 09 '22 at 23:45
  • Honestly, I thought you claim about freestanding environments had to do with not having available the optimised implementation in glibc. Obviously, I didn't understand your reasoning, which is why it probably would have been better to clarify a bit more, along with clarifying the fact that you're specifically talking about certain platforms which lack hardware support for C's mandated integer types. – rici Jun 09 '22 at 23:48
  • read the answer again. Freestanding has no printf. And for printf you need division by 10 and might not want or can't use libgcc's udivmod3. That's the motivation. – Goswin von Brederlow Jun 10 '22 at 00:00