16
$\begingroup$

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?

$\endgroup$
  • 3
    $\begingroup$ See the related question's answer: cs.stackexchange.com/a/30047/28999. By the way, signed -7 is not represented as 1111. That's -1. Then e.g. 1111 - 0001 = 1110 in both the signed and unsigned case (-2 vs 14) $\endgroup$ – Albert Hendriks Mar 10 at 15:27
  • 2
    $\begingroup$ @AlbertHendriks In fairness, some older computers do use a "sign-magnitude representation" (one sign bit and $n-1$ magnitude bits), and we still use that style for e.g. IEEE floats. They're just inelegant and hard to work with compared with two's-complement. $\endgroup$ – Draconis Mar 10 at 15:41
  • 1
    $\begingroup$ The main difference is in how the greaterThan/lessThan operators behave, and whether right-shift fills in with the highest bit. When you actually multiply and divide, the result is the same. $\endgroup$ – Rob Mar 10 at 20:47
  • 5
    $\begingroup$ Possible duplicate of How are Signed integers different from unsigned integers once compiled $\endgroup$ – David Richerby Mar 11 at 11:33
  • 2
    $\begingroup$ @Rob That's not entirely correct. Addition, subtraction, and multiplication are all the same between unsigned and twos complement - assuming your inputs and outputs are the same size. Division is not the same 6/2 is 3, but -2/2 is -1. And many CPUs have multiplication instructions where the two inputs are of identical size, but the output is twice the size, in which case unsigned and twos complement are not the same either. $\endgroup$ – kasperd Mar 11 at 16:20
14
$\begingroup$

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 MUL, DIV, ADD, SUB interpret the bit pattern as some kind of number, whereas logical instructions such as AND, OR, 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.

$\endgroup$
  • $\begingroup$ Note that the Java bytecode also counts as an ISA. And it pretty much does care about data types... $\endgroup$ – John Dvorak Mar 11 at 18:45
  • $\begingroup$ Java bytecode does sort-of count as an ISA, in the sense that it's been implemented in silicon. However, basic-type checking of this kind is checking performed by the classloader, so the types can mostly be ignored at run-time. And of course Java bytecode doesn't have unsigned types in the first place. $\endgroup$ – Pseudonym Mar 12 at 1:15
  • $\begingroup$ @Pseudonym: Well, technically, it does have char, which is a 16-bit unsigned type. Of course, there are still no unsigned arithmetic instructions in Java bytecode, since any char values are automatically promoted to int (32-bit signed) for arithmetic. $\endgroup$ – Ilmari Karonen Mar 12 at 10:28
42
$\begingroup$

Short version: it doesn't know. There's no way to tell.

If 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.

$\endgroup$
  • $\begingroup$ Ah, gotcha. Thanks for this :) $\endgroup$ – noorav Mar 11 at 2:09
  • 10
    $\begingroup$ In two's complement representation three arithmetic operations are signedness-agnostic: addition, subtraction and multiplication (with product of the same length as operands). Only division has to be handled differently for signed operands. $\endgroup$ – Ruslan Mar 11 at 7:04
  • 4
    $\begingroup$ There's also comparison: < <= >= > are different for signed vs. unsigned operands whereas == and != are signedness-agnostic. $\endgroup$ – Daniel Schepler Mar 11 at 21:01
  • $\begingroup$ Multiplication often has signed and unsigned varieties: 0xFFFFFFFF * 0xFFFFFFFF is 0xFFFFFFFE00000001 if unsigned and 0x0000000000000001 if signed. Processors like Intel's return the result in 2 registers, and the top register does differ for signed and unsigned. The bottom register is 1 in both situations. $\endgroup$ – Rudy Velthuis Mar 12 at 15:37
9
$\begingroup$

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 CMP with 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.

$\endgroup$
  • 1
    $\begingroup$ Even multiplication is the same for unsigned and signed (two's complement) integers, as long as you don't need the result to have more bits than the inputs. If you're doing something like 8 × 8 → 16 bit (or 16 × 16 → 32 bit, etc.) multiplication, however, you do need to sign extend the inputs (or the intermediate results). $\endgroup$ – Ilmari Karonen Mar 12 at 10:20
  • $\begingroup$ @IlmariKaronen This is true; ARM A32/A64 are instruction sets that have many forms of the multiply instruction, including sign-agnostic multiply-add that writes only the lower-order bits, but also smulh and umulh that return only the upper bits of the multiplication and signed and unsigned instructions that return the result in a register twice as wide as the source operands. $\endgroup$ – Davislor Mar 12 at 14:45
6
$\begingroup$

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.

$\endgroup$
  • $\begingroup$ There's no such thing as a char at the hardware level. Maybe once upon a time, back in the days of mechanical teleprinters. But today, a char is just a number as far as the hardware is concerned. The reason why different numbers correspond to different letter shapes on your screen is that those numbers are used to select different bitmaps or different drawing routines from a big table (i.e., from a "font"). $\endgroup$ – Solomon Slow Mar 11 at 1:52
3
$\begingroup$

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.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.