Is there a way to implement the Left Arithmetic Shift and Right Arithmetic Shift, using only operations AND, OR, NOT, XOR?
In each of the operations AND, OR, NOT, and XOR, each bit in the result is solely a function of the one (OT) or two (AND, OR, XOR) bits in the same position in the operands. In a shift by any amount other than zero, each bit in the result is a function of a bit in a different position in the operand being shifted. Therefore, it is not possible to compute a shift solely from AND, OR, NOT, and XOR.
Consider a = 0b0011.
Then we have ~a = 0b1100.
We also have a | ~a = 0b1111.
And also a & ~a = 0b0000.
You can manually check all possible combinations of &, ^, ~, and | to see that we can't make anything more than those four binary values. None of which are 0b0110 (what we want from left shift) or 0b0001 (what we want from right shift).
Since we found a number for which it can't be done, then we know in general it can't be done.
IT IS POSSIBLE
Basically, the main logic of shifting is using shifters (barrel shifter and barrel shifter with multiplexers) which in turn can be created using multiplexers. And you can create multiplexers easily using the logical operations that you mentioned. Here is 2-to-1 MUX created with NAND gates: Mux with NAND
Related
We need to find new bits turned ON in interlock status received from the device compared to the last status read. This is for firing error codes for bits that are newly set. I am using the following statement.
bits_on =~last_status & new_status;
Is there any better ways to do this?
It's only 2 operations and an assignment, so the only way to improve it would be to do it in 1 operation and an assignment. It doesn't correspond to any of the simple C bit manipulation operators, so doing it in 1 operation is not possible.
However, depending on your architecture your compiler might actually already be compiling it to a single instruction.
ANDN (Logical AND NOT) is part of the BMI1 instruction set, and is equivalent to ~x & y.
It seems it should be a straightforward to shift/rotate an array by n bits.
However, the programming language I'm using (Solidity) doesn't have any such operator (i.e. there's no shift or rotate operator)...
I have an unsigned, 256-bit integer (which is a Solidity type uint256).
I was wondering if I could somehow do a shift or rotate operation "manually"?
I mean, perform some series of multiplication (*), mod (%) or similar operations to give the desired shift and rotate? I know this could be very inefficient, but I only need to do this operation once or twice an hour so it doesn't matter in my use-case.
If there isn't a shift function then you will likely have to do some series of *2 or slightly better would be:
val*[2^(number of shifts)]
Context
I am using a lot of bitwise operations but I don't even know how they are implemented at the lowest level possible.
I would like to see how the intel/amd devs achieve to implement such operations. Not to replace them in my code, that would be dumb.. But to get a broader catch on what is going on.
I tried to find some info but most of the time, people ask about its use or to replace it with other bitwise operations, which is not the case here.
Questions
Is it doing basic iterations in assembly(sse) over the 32 bits and compare ?
Are there some tricks to get it up to speed ?
Thanks
Most all are implemented directly on the CPU, as basic, native instructions, not part of SSE. These are the oldest, most basic operations on the CPU register.
As to how and, or, xor, etc. are implemented, if you are really interested, look up digital logic design, or discrete math. Lookup up Flip-flops, AND gates, or NAND / NOR / XOR gates
https://en.wikipedia.org/wiki/NAND_logic
Also lookup K-maps (Karnaugh maps), these are what you can use to implement a logic circuit by hand.
https://en.wikipedia.org/wiki/Karnaugh_map
If you really enjoy the reading, you can signup for a digital logic design class if you have access to an engineering or computer science university. You will get to build logic circuits with large ICs on a breadboard, but nowadays most CPUs are "written" with code, like software, and "printed" on a silicon wafer.
Of particular interest is NAND and NOR due to their functional completeness (you can use NAND or NOR to construct any truth table).
NAND (logical symbol looks like =Do-)
A
=Do- Q is Q = NOT(A AND B)
B
Truth table
A B Q
0 0 1
0 1 1
1 0 1
1 1 0
You can rewrite any logic with NAND.
As you can also see, its pretty efficient, you can't get any lower level than a single gate with binary (though there is ternary / tri-state logic), so its a single clock state change. So for a 64-bit CPU register, you'll need 64 of these babies side by side, PER register... PER core... PER instruction. And that is only the "logical" registers. Because advanced processors (like Intel Core) do register renaming, you have more physical registers in silicon than logically available to you by name.
AND, OR, XOR, and NOT operations are implemented quite efficiently in silicon, and so are generally a single-cycle native instruction on most processors. That is, for a 16-bit processor, whole 16-bit registers are ANDed at once; on a 32-bit processor, 32 bits at once, etc. The only performance issue you might want to be aware of is alignment: on an ARM processor, for example, if a 32-bit value starts at a memory address that is a multiple of 4, then a read-modify-write can be done in two or three cycles. If it's at an odd address, it has to do two reads at the neighboring aligned addresses and two writes, and so is slower.
Bit shifting in some older processors may involve looping over single shifts. That is, 1 << 5 will take longer than 1 << 2. But most modern processors have what is called a "barrel shifter" that equalizes all shifts up to the register size, so on a Pentium, 1 << 31 takes no longer than 1 << 2.
Addition and subtraction are fast primitives as well. Multiplication and division are tricky: these are mostly implemented as microcode loops. Multiplication can be sped up by unrolling the loops into huge swaths of silicon in a high-end processor, but division cannot, so generally division is the slowest basic operation in a microprocessor.
Bitwise operations are what processors are made of, so it is natural to expose those operations with instructions. Operations like AND, OR, XOR, NOR, NAND and NOT can be performed by the ALU with only a few logic gates per bit. Importantly, each bit of the result only relies on two bits of the input (unlike multiplication or addition), so the entire operation can proceed in parallel without any complication.
As you know, data in computers is represented in a binary format.
For example, if you have the integer 13 it's represented as 1101b (where b means binary). This works out to (1) * 8 + (1) * 4 + (0) * 2 + (1) * 1 = 13, just like (1) * 10 + (3) * 1 = 13 -- different bases.
However, for basic operations computers need to know how much data you're working with. A typical integer size is 32 bits. So it's not just 1101b, it's 00000000000000000000000000001101b -- 32 bits, most of them unused.
Bitwise operations are just that -- they operate only on a bit level. Adding, multiplying, and other operations consider multiple bits at a time to perform their function, but bitwise operators do not. For example:
What's 12 bitwise-and 7? (in C vernacular, 12 & 7)
1010b 12 &
0111b 7
----- =
0010n 2
Why? Think vertically! Look at the left set of digits -- 1 and 0 is 0. Then, 0 and 1 is 0. Then, 1 and 1 is 1. Finally, 0 and 1 is 0.
This is based on the and truth table that states these rules -- that only true (aka 1) and true (aka 1) results in false (aka 0). All other resultant values are false (aka 0).
Likewise, the or truth table states that all results are true (aka 1) except for false (aka 0) and false (aka 0) which results in false (aka 0).
Let's do the same example, but this time let's computer 12 bitwise-or 7. (Or in C vernacular, 12 | 7)
1010b 12 |
0111b 7
----- =
1111n 15
And finally, let's consider one other principal bitwise operator: not. This is a unary operator where you simply flip each bit. Let's compute bitwise-not 7 (or in C vernacular, ~7)
0111b ~7
----- =
1000b 8
But wait.. What about all those leading zeroes? Well, yes, before I was omitting them because they weren't important, but now they surely are:
00000000000000000000000000000111b ~7
--------------------------------- =
11111111111111111111111111111000b ... big number?
If you're instructing the computer to treat the result as an unsigned integer (32-bit), that's a really big number. (Little less than 4 billion). If you're instructing the computer to treat the result as a signed integer (32-bit) that's -8.
As you may have guessed, since the logic is really quite simple for all these operations, there's not much you can do to make them individually faster. However, bitwise operations obey the same logic as boolean logic, and thus you can use boolean logic reduction techniques to reduce the number of bitwise operations you may need.
e.g. (A & B) | (A & C) results in the same as A & (B | C)
However, that's a much larger topic. Karnaugh maps are one technique, but boolean algebra is usually what I end up using while programming.
Does endianness matter at all with the bitwise operations? Either logical or shifting?
I'm working on homework with regard to bitwise operators, and I can not make heads or tails on it, and I think I'm getting quite hung up on the endianess. That is, I'm using a little endian machine (like most are), but does this need to be considered or is it a wasted fact?
In case it matters, I'm using C.
Endianness only matters for layout of data in memory. As soon as data is loaded by the processor to be operated on, endianness is completely irrelevent. Shifts, bitwise operations, and so on perform as you would expect (data logically laid out as low-order bit to high) regardless of endianness.
The bitwise operators abstract away the endianness. For example, the >> operator always shifts the bits towards the least significant digit. However, this doesn't mean you are safe to completely ignore endianness when using them, for example when dealing with individual bytes in a larger structure you cannot always assume that they will fall in the same place.
short temp = 0x1234;
temp = temp >> 8;
// on little endian, c will be 0x12, on big endian, it will be 0x0
char c=((char*)&temp)[0];
To clarify, I am not in basic disagreement with the other answers here. The point I am trying to make is to emphasise that although the bitwise operators are essentially endian neutral, you cannot ignore the effect of endianess in your code, especially when combined with other operators.
As others have mentioned, shifts are defined by the C language specification and are independent of endianness, but the implementation of a right shift may vary depending on iff the architecture uses one's complement or two's complement arithmetic.
It depends. Without casting the number into a new type, you can treat the endianness transparently.
However, if your operation involves some new type casting, then use your caution.
For example, if you want right shift some bits and cast (explicitly or not) to a new type, endianness matters!
To test your endianness, you can simply cast an int into a char:
int i = 1;
char *ptr;
...
ptr = (char *) &i; //Cast it here
return (*ptr);
You haven't specified a language but usually, programming languages such as C abstract endianness away in bitwise operations. So no, it doesn't matter in bitwise operations.
If the bit pattern corresponding to a signed integer is shifted to the right then
1 vacant bit will be filled by the sign bit
2 vacant bit will be filled by 0
3 The outcome is implementation dependent
4 none of the above
The answer to this question is 3rd option.. Can anybody explain this,,
Also give some basic idea, about the theory behind left shift and right shift operators in C programming. E.g.
what is filled on the vacant bit when any of the operation is performed. I checked and noticed that left shifting fills the vacant bit by 0 and right shift fills by 1. Please clear the logic...
I'd have to check the spec for the question of what precisely is implementation dependent.
However, every implementation I've used in (mumble) years of embedded systems projects has been sensible:
Left shifts always shift in a 0 at the low bit. No other value makes sense.
Right shifts depend on the data type. A right shift of a signed integer duplicates the high bit as it shifts the rest to the right. This is called an "arithmetic shift", and has the nice property (in twos complement arithmetic, at least) that it divides the value by two while preserving the sign of the original number.
A right shift of an unsigned integer shifts a 0 into the high bit, and is usually known as a "logical shift".
It makes sense for an implementation to provide both kinds of shifts because both are useful, and using signed/unsigned to select which is meant is a sensible choice.
Edit: At least one thing that absolutely is implementation dependent is that the C standard does not (completely) specify the underlying implementation of integers and their storage. For instance, it is possible to build a compliant C compiler for a machine that uses one's complement arithmetic. It would also be possible (I think) to build a compliant compiler for a machine whose native storage was signed magnitude BCD. (Nope, I was wrong, see below.)
In practice, the world is pretty much settled on two's complement binary for the CPU and some of the pedantry is mooted.
So part of the question really is: how do you define the meaning of the << and >> operators in a way that is stable regardless of the underlying arithmetic system used.
IIRC, the definition of n<<1 is effectively n*2, and n>>1 is effectively n/2, with a natural extension to shifts by more than 1 (but not more than 31... there be undefined dragons there...) and with the notion that the >> operator will preserve the sign if operating on a signed value.
Edit 2: Pete Kirkham points out in his fine answer that the C standard does specifically disallow the scary case of a BCD representation of integers, whether it is signed magnitude or ten's complement. I'm sure that is a good thing, even if Knuth did use a (optionally) BCD machine for his sample code in early editions of The Art of Computer Programming.
In those rare use cases where BCD is the right answer, then storing them in an unsigned long (8 digits ten's complement) or an unsigned 64-bit integer (room for 16 digits ten's complement or 15 digits plus sign and flags) and using a carefully crafted arithmetic library to manipulate them makes sense.
In practice, of course, C implementations map the operators as directly as allowed by the standard to the native machine instructions of the CPU. The folk who wrote the standard were very mindful of the existence of of many ways to implement even simple things like the representation of an integral value, and the C standard reflects that by allowing just enough implementation defined behavior to let operators be efficiently implemented in each machine.
The alternative leads swiftly to a world where all math operations are completely specified, and cannot be efficiently implemented on any machine.
C does not guarantee that there is a sign bit, or anything about the bit-level representation of integers, that is why.
For two's complement, you will typically see the sign bit being shifted in, but that is up to the implementation.
ISO C99 requires a sign bit somewhere in the representation, but gives the option between various compliment/sign and magnitude schemes and allows padding bits, all of which effect the operation of >>.
Section 6.2.6.2 (Integer types)
For signed integer types, the bits of the object representation shall be divided into three groups: value bits, padding bits, and the sign
bit. There need not be any padding bits; there shall be exactly one
sign bit.
and
Section 6.5.7 (Bitwise shift operators)
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative
value, the value of the result is the integral part of the quotient of
E1 / 2E2 . If E1 has a signed type and a negative value,
the resulting value is implementation-defined.
It doesn't specify which of 1's compliment, 2's compliment or sign and magnitude is used, nor whether the sign bit is left or right of the value bits, or where any padding is, all of which would effect the output of the >> operator on signed negatives.
In answer to RBerteig's query, C99 precludes a BCD representation of integers:
Section 6.2.6.2 (Integer types)
If there are N value bits, each bit shall represent a different power of 2 between 1 and 2 N −1 , so that objects of that type shall be
capable of representing values from 0 to 2 N − 1 using a pure binary
representation; this shall be known as the value representation.
The C language implementations tend to map bit shifting operations directly onto the corresponding machine code instructions. Since different hardware architectures have historically done different things, the C specification tends to leave things implementation defined so that C implementations can take advantage of whatever the hardware offers.
The outcome is implementation dependent. However, in practice, every single x86, PPC, and MIPS compiler I have ever worked with has followed this rule for shifting right:
If the operand is a signed integer,
the vacant bit is filled with the
sign bit (really the most
significant bit)
If the operand is
an unsigned integer, the vacant bit
is filled with zero.
As RBerteig says, this is so that for signed integers, n >> 1 = n/2 (rounded down) for both positive and negative n, and for unsigned integers, n >> 1 = n/2 even for n > 2^31 (on a 32-bit architecture).
The corresponding hardware instructions are arithmetic (sign-extending) and logical (not sign-extending) shift; the compiler chooses between them based on whether the operand is signed or unsigned.