How would the ALU in a microprocessor differentiate between a signed number, -7 that is denoted by 1111 and an unsigned number 15, also denoted by 1111?
The short and simple answer is: it doesn't. No modern mainstream CPU ISA works the way you think it does.
For the CPU, it's just a bit pattern. It's up to you, the programmer, to keep track of what that bit pattern means.
In general, ISAs do not distinguish between different data types, when it comes to storage. (Ignoring special-purpose registers such as floating registers in an FPU.) It's just a meaningless pattern of bits to the CPU. However, ISAs do have different kinds of instructions that may interpret the bit pattern in different ways. For example, arithmetic instructions such as
SUB interpret the bit pattern as some kind of number, whereas logical instructions such as
XOR interpret it as an array of booleans. So, it is up to, the programmer, (or the author of the interpreter or compiler if you use a higher-level language) to choose the correct instructions.
There may very well be separate instructions for signed vs. unsigned numbers, for example. Some ISAs also have instructions for arithmetic with binary-coded decimals.
However, note that I wrote "modern mainstream ISA" above. There are in fact non-mainstream or historic ISAs that work differently. For example, both the original 48-bit CISC ISA of the IBM AS/400 as well as the current POWER-based 64-bit RISC ISA of the system now called IBM i, distinguish between pointers and other values. Pointers are always tagged, and they include type information and rights management. The CPU knows whether a value is a pointer or not, and only the privileged i/OS kernel is allowed to manipulate pointers freely. User applications can only manipulate pointers they own to point at memory they own using a small number of safe instructions.
There were also some historic ISA designs that included at least some limited form of type-awareness.
Short version: it doesn't know. There's no way to tell.
1111 represents -7, then you have a sign-magnitude representation, where the first bit is the sign and the rest of the bits are the magnitude. In this case, arithmetic is somewhat complicated, since an unsigned add and a signed add use different logic. So you'd probably have a
SADD and a
UADD opcode, and if you choose the wrong one you get nonsensical results.
More often, though,
1111 represents -1, in what's called a two's-complement representation. In this case, the ALU simply doesn't care if the numbers are signed or unsigned! For example, let's take the operation of
1110 + 0001. In signed arithmetic, this means "-2 + 1", and the result should be -1 (
1111). In unsigned arithmetic, this means "14 + 1", and the result should be 15 (
1111). So the ALU doesn't know whether you want a signed or an unsigned result, and it doesn't care. It just does the addition as if it were unsigned, and if you want to treat that as a signed integer afterward, that's up to you.
EDIT: As Ruslan and Daniel Schepler quite rightly point out in the comments, some operands still need separate signed and unsigned versions, even on a two's-complement machine. Addition, subtraction, multiplication, equality, and such all work fine without knowing if the numbers are signed or not. But division and any greater-than/less-than comparisons have to have separate versions.
EDIT EDIT: There are some other representations too, like one's-complement, but these are basically never used any more so you shouldn't have to worry about them.
One of the great advantages of two’s-complement math, which all modern architectures use, is that the addition and subtraction instructions are exactly the same for both signed and unsigned operands.
Many CPUs do not even have multiply, divide or modulus instructions. If they do, they must have separate signed and unsigned forms of the instruction, and the compiler (or the assembly-language programmer) chooses the appropriate one.
CPUs also generally have different instructions for signed or unsigned comparisons. For example, x86 might follow a
JL (Jump if Less than) if the comparison should be signed, or
JB (Jump if Below) if the comparison should be unsigned. Again, the compiler or the programmer would choose the right instruction for the data type.
A few other instructions often come in signed and unsigned variants, such as right shift or loading a value into a wider register, with or without sign-extension.
It doesn't. The processor relies on the instruction set to tell it what type of data it is looking at and where to send it. There's nothing about 1s and 0s in the operand itself that can inherently signal to the ALU whether the data is a char, float, int, signed int, etc. If that 1111 is going to an electric circuit that's expecting a 2s complement, it's going to be interpreted as a 2s complement.
I'd like to give an addition to the answers already made:
In most other answers it is noted that in twos-complement arithmetic the result is the same for signed and unsigned numbers:
-2 + 1 = -1 1110 + 0001 = 1111 14 + 1 = 15 1110 + 0001 = 1111
However, there are exceptions:
Division: -2 / 2 = -1 1110 / 0010 = 1111 14 / 2 = 7 1110 / 0010 = 0111 Comparison: -2 < 2 = TRUE 1110 < 0010 = TRUE 14 < 2 = FALSE 1110 < 0010 = FALSE "Typical" (*) multiplication: -2 * 2 = -4 1110 * 0010 = 11111100 14 * 2 = 28 1110 * 0010 = 00011100
(*) On many CPUs the result of a multiplication of two n-bit numbers is (2*n) bits wide.
For such operations the CPUs have different instructions for signed and unsigned arithmetic.
This means the programmer (or the compiler) must use other instructions for signed and for unsigned arithmetic.
The x86 CPU for example has an instruction named
div for doing an unsigned division and an instruction named
idiv for doing a signed division.
There are also different "conditional" instructions (conditional jumps, set-bit-on-condition) as well as multiplication instructions for signed and unsigned arithmetic.