1

I was reading a text book saying:

It is important to note how machine code distinguishes between signed and unsigned values. Unlike in C, it does not associate a data type with each program value. Instead, it mostly uses the same (assembly)instructions for the two cases, because many arithmetic operations have the same bit-level behavior for unsigned and two’s-complement arithmetic.

I don't understand what it means, could anyone provide me an example?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • 3
    The CPU does not distinguish signed from unsigned values and doesn't know if a value is meant to be signed or unsigned. You as an assembly programmer have to do so yourself. – fuz Aug 21 '18 at 07:51
  • 1
    That really depends on the CPU architecture. Some have arithmetic and other instructions which handle "signed" and "unsigned" values differently. – Some programmer dude Aug 21 '18 at 08:07
  • 2
    Related, possible duplicate: [Which arithmetic operations are the same on unsigned and two's complement signed numbers?](https://stackoverflow.com/q/21475286), and also [Why would we use addiu instead of addi?](https://stackoverflow.com/q/36274590) – Peter Cordes Aug 21 '18 at 08:17
  • 1
    Also related: [Signed versus Unsigned Integers](https://stackoverflow.com/q/247873). – Peter Cordes Aug 21 '18 at 08:25
  • 2
    @Lundin in x86 ADD and ADC **always** set the carry flag. The difference is just that ADC also uses the carry flag as input while ADD doesn't – phuclv Aug 21 '18 at 11:14
  • 1
    @phuclv Ah yeah that's true, I'm far from an x86 expert. Anyhow, my point is that the signedness resides in various flags in a condition code register, rather than in the value itself. – Lundin Aug 21 '18 at 11:19
  • @Lundin actually no architectures I know have a flag for signed or unsigned operations. The flags are for overflow, carry, parity... check. Note that they have a sign flag, but only for checking if the sign bit is set or not. It doesn't mean that the value is signed. Only a few instructions need to differentiate between signed and unsigned operations, and the signness is encoded in the instruction name – phuclv Aug 21 '18 at 11:24
  • @phuclv Arithmetic right shift certainly cares about signed vs unsigned. But indeed it does not make much sense to use that instruction unless the data is signed. – Lundin Aug 21 '18 at 11:28
  • 1
    @Lundin that's why there are different instructions for arithmetic and logical right shift. See [signed and unsigned arithmetic implementation on x86](https://stackoverflow.com/q/25241477/995714) – phuclv Aug 21 '18 at 11:31
  • @Lundin "Typically the CPU does separate the two." -- the INTEL and some others have two instructions, indeed. The 6502 only had the `ADC`. The MIPS only had the `ADD`. – Alexis Wilke Aug 21 '18 at 23:12
  • @AlexisWilke: MIPS doesn't even *have* a carry flag (or a flags register at all), so the only option would have been a compare-and-add with 4 inputs or something. (`sum_hi = x_hi + y_hi + (sum_lo < x_lo)`). MIPS actually does have `add` vs. `addu`, but the "unsigned" is a misnomer. You only use `add` when you want it to trap on signed overflow (i.e. almost never), otherwise you use `addu` for signed and unsigned. [Why would we use addiu instead of addi?](https://stackoverflow.com/q/36274590). I think every mainstream ISA with a carry flag has an `adc`. Some toy ISAs might not. – Peter Cordes Aug 21 '18 at 23:54
  • 1
    @Lundin: I'd recommend you delete that first comment about add vs. add-with-carry. Unfortunately nothing in that comment is correct for any ISA I've ever heard of; like \@phuclv said the difference is only carry being an input or not. Since it's potentially confusing for beginners, I'd say delete it. Setting "a carry flag based on two's complement representation" makes no sense either; carry flags are for carry-out from the top bit, which normally indicates *unsigned* overflow. 2's complement signed overflow is different. http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt – Peter Cordes Aug 22 '18 at 00:00
  • @Lundin your ADD vs ADC statement is very much incorrect, please remove. Now if you were referring to the signed overflow flag vs the unsigned overflow flag (also known as carry or carry out) are computed differently by an add operation (or subtract since they use the same addition logic deep down) from each other and are used for signed vs unsigned follow on operations where sign does matter. the add itself doesnt know nor care, part of the beauty of twos complement and has absolutely nothing to do with x86, just simple binary math. – old_timer Aug 22 '18 at 03:19
  • another beauty of twos complement is that the add and sub can use the same addition logic, if subtract invert the second operand and the carry in. some instruction sets invert the carry out to call it a borrow some dont. and in that sense if your instruction set has a subtract with carry or subtract with borrow whether or not that carry in is inverted or not depends on that instruction sets definition of carry out on a subtraction (inverted or not). elementary style pencil and paper math easily demonstrates all of this. – old_timer Aug 22 '18 at 03:21
  • when you start addition with grade school paper and pencil math the assumption is that the carry on on the ones column is zero and left blank. the difference between add and add with carry is that you dont make that assumption and use a carry in from the carry flag so it might be a one and that certainly changes the result than if it were a zero like a normal add. – old_timer Aug 22 '18 at 03:23
  • mips made the colossal mistake of naming an instruction "add unsigned" second or third instruction in a mips reference depending on how listed. I refused to learn mips for a long time, how clueless were these people? Later gave in, learned it, and figured out it was just horribly named. so dont let the add unsigned in mips confuse you. the "result" of addition (or subtraction) is not affected by signedness. the flags if the ISA has flags may have a signed overflow flag which is only useful for signed use of that operation. – old_timer Aug 22 '18 at 03:31
  • you can have an add without an adc and vice versa just takes more instructions to perform the operation. its a logic vs software tradeoff. you can clear the carry flag then do an adc that is the same as an add. you can do an add, branch if carry clear over an inc/add 1 instruction. – old_timer Aug 22 '18 at 03:35

4 Answers4

6

For example, this code:

int main() {
    int i = -1;
    if(i < 9)
        i++;
    unsigned u = -1;  // Wraps around to UINT_MAX value
    if(u < 9)
        u++;
}

gives following output on x86 GCC:

main:
    push    rbp
    mov     rbp, rsp
    mov     DWORD PTR [rbp-4], -1 ; i = -1
    cmp     DWORD PTR [rbp-4], 8  ; i comparison
    jg      .L2                   ; i comparison
    add     DWORD PTR [rbp-4], 1  ; i addition
.L2:
    mov     DWORD PTR [rbp-8], -1 ; u = -1
    cmp     DWORD PTR [rbp-8], 8  ; u comparison
    ja      .L3                   ; u comparison
    add     DWORD PTR [rbp-8], 1  ; u addition
.L3:
    mov     eax, 0
    pop     rbp
    ret

Notice how it uses the same instructions on intialization (mov) and increment (add) for variables i and u. This is because the bit pattern changes identically for unsigned and 2's complement.

Comparison also uses the same instruction cmp, but jump decision has to be different, because values where the highest bit is set are different on the types: jg (jump if greater) on signed, and ja (jump if above) on unsigned.

What instructions are chosen, depends on the architecture and the compiler.

user694733
  • 15,208
  • 2
  • 42
  • 68
  • 3
    You can avoid all that noise of un-optimized code if you make `i` and `u` function args, so you can compile with optimization enabled and not have them optimize away. (If you return or store the result). See https://godbolt.org/z/muqTFb. (I used C++ `int &i` to pass by reference without changing everything to `*i`). – Peter Cordes Aug 21 '18 at 08:24
5

On Intel Processors (x86 family) and others that have FLAGS, you get bits in those FLAGS that tell you how the last operation worked. The name of the FLAGS vary a little between processors, but in general you have two important ones in regard to arithmetic: CF and OF.

CF is the Carry bit (often called C on other processors).

OF is the Overflow bit (often called V on other processors).

More or less, CF represents an unsigned overflow and OF represents a signed overflow. When the processors does the ADD operation, it has one extra bit, which is CF. So, if you add two 64 bit numbers, the result without wrapping may need 65 bits. That is the carry. The OF flag is set to the highest bit (so bit 63 in a 64 bit number), using 3 logical operations against that bit in the two sources and the destination.

There is an example of how CF works with 4 bit registers:

R1 = 1010
R2 = 1101

R3 = R1 + R2 = 1 0111
               ^
               +---- carry (CF)

The extra 1 doesn't fit in R3 so it gets put in the CF bit instead. As a side note, the MIPS processor does not have any FLAGS. It's up to you to determine whether a carry is generated (which you can do using XOR and such on the two sources and the destination).

However, in C (and C++), there is no verification of overflow on your integer types (at least not by default.) So in other words, the CF and OF flags are ignored for all your operations except the four compare operators (<, <=, >, >=).

As shown in the example presented by @user694733, the difference is whether a jg or ja will be used. Each of the 16 jump instructions will test various flags to know whether to jump or not. That combination is really what makes the difference.

Another interesting aspect is the difference between ADC and ADD. In one case you add with the carry and the other you don't. It's probably not used as much now that we have 64 bit computers, but to add two 64 bit numbers with a 32 bit processor, it would add the lower 32 bits as unsigned 32 bit numbers and then add the upper 32 bit numbers (signed or unsigned as may be the case) plus the carry from the first operation.

Say you have two 64 bit numbers in 32 bit registers (ECX:EAX and EDX:EBX), you would add them like this:

ADD EAX, EBX
ADC ECX, EDX

Here the EDX and the carry are added to ECX if EAX + EBX had an unsigned overflow (carry--meaning that adding EAX and EBX properly should be represented by 33 bits now because the result doesn't fit 32 bits, the CF flag is that 33rd bit).

To be noted, the Intel processors have:

  • A Zero bit: ZF (whether the result is zero or not,)
  • CF is called "Borrow" when subtracting (for SBC, SBB,) and
  • Also the AF bit which is used for "decimal number operations" (which no one in their right mind uses.) That AF bit tells you that there is an overflow in the decimal operation. Something like that. I never used that one. I find their use too complicated/cumbersome. Also, the bit is sill there in amd64 but the instructions setting it were removed (see DAA for example).
Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • If you're going to use the ARM names for the flags, you might as well go ahead and use ARM instructions like `add r0, r1, r2`. The question doesn't mention x86, only some of the other answers. x86 calls them OF and CF. (https://en.wikipedia.org/wiki/FLAGS_register). But yes, good point that ARM `sub` uses C as a carry instead of a borrow, opposite of x86 where `sbb` uses CF if there's a borrow from the top bit. Although it looks like you're claiming that x86 has an *extra* flag for borrow separate from carry, which is not the case. [`sbb`](http://felixcloutier.com/x86/SBB.html) uses CF. – Peter Cordes Aug 22 '18 at 00:12
  • @PeterCordes Thanks for the comment! I applied corrections. I don't program with assembly on a daily basis so it tends to fade out a bit... :-) – Alexis Wilke Aug 22 '18 at 02:43
  • 1
    arm documentation also clarifies unsigned less than from signed less than, etc and what flags are involved in each. not always the case with other instruction sets and their documentation. leading to a lot of confusion as to which conditional to use, likewise a number of them dont tell you which is subtracted from which to determine the less than or greater than...sadly. – old_timer Aug 22 '18 at 03:02
  • The AF half-carry *bit* wasn't removed in AMD64; AFAIK it's still there if you `pushf` / `pop rax`. The instructions other than push which read it, like `DAA`, were removed. AF is set when there's a carry from bit #3 to bit #4, low to high nibble. The actual decimal logic is/was implemented by instructions like [`DAA`](https://www.felixcloutier.com/x86/daa), e.g. pseudocode like `IF (((AL AND 0FH) > 9) or AF = 1)`. Programs storing numbers in BCD format are rare, and even in 16 / 32-bit mode the instructions aren't super fast. I've never wanted to use them for that. – Peter Cordes May 31 '21 at 20:48
2

The beauty of twos complement is that for addition (and as a result subtraction since that uses an adder, again part of the beauty of twos complement). That the add operation itself does not care about signed vs unsigned the same bit patterns added together produce the same result 0xFE + 0x01 = 0xFF, -2 + 1 = 1 also 126 + 1 = 127. same input bits same result pattern.

Twos complement helps for only a percentage. Not all. add/subtract but not necessarily multiply and divide. Bitwise of course bits is bits. But (right) shifts desire a difference, but does C deliver?

The comparisons are very sensitive. The equal and not equal, zero and not zero those are single flag tests and will work. But unsigned less than and signed less than are not the same set of flags that are used/tested. The less than and greater than with or without equal applied to them do not work the same way with unsigned vs signed. Likewise signed overflow and unsigned overflow (often just called the carry bit) are computed differently from each other. And some instruction sets the carry bit is inverted when the operand is a subtract, but not always, so for comparisons you need to know whether or not it is a borrow bit on subtract or always just the carry out unmodified.

Multiplication and likely division are "it depends". An N bit times N bit equals N bit result signed and unsigned both work, but N bit times N bit equals 2*Nbit (the only really useful hardware multiply) requires a signed and unsigned version to have the hardware/instruction do all the work, otherwise you have to break the operands up into parts if you don't have both flavors. A simple paper and pencil grade school will show why, leave that to the reader to figure out.

You don't need us at all you can easily provide your own example and see from the compiler output when there is a difference and when there isn't.

int32_t fun0 ( int32_t a, int32_t b ) { return a+b; }
int32_t fun1 ( int32_t a, int32_t b ) { return a*b; }
int32_t fun2 ( int32_t a, int32_t b ) { return a^b; }

uint32_t fun3 ( uint32_t a, uint32_t b ) { return a+b; }
uint32_t fun4 ( uint32_t a, uint32_t b ) { return a*b; }
uint32_t fun5 ( uint32_t a, uint32_t b ) { return a^b; }

uint32_t fun6 ( uint64_t a, uint64_t b ) { return a+b; }
uint32_t fun7 ( uint64_t a, uint64_t b ) { return a*b; }
uint32_t fun8 ( uint64_t a, uint64_t b ) { return a^b; }

uint64_t fun9 ( uint64_t a, uint64_t b ) { return a*b; }
int64_t fun10 ( int64_t a, int64_t b ) { return a*b; }
uint64_t fun11 ( uint32_t a, uint32_t b ) { return a*b; }
int64_t fun12 ( int32_t a, int32_t b ) { return a*b; }

int32_t comp0 ( int32_t a, int32_t b ) { return a<b; }
uint32_t comp1 ( uint32_t a, uint32_t b ) { return a<b; }

plus other operators and combinations.

EDIT

Okay the real answer...rather than making you do the work.

I want to add -2 and +1

  11111110
+ 00000001
============

finish it

  00000000
  11111110
+ 00000001
============
  11111111

-2 + 1 = -1

What about 127 + 1

  00000000
  11111110
+ 00000001
============
  11111111

hmmm...same bits in same bits out, but how I interpret those bits as a programmer varies widely.

You can try as many legal values as you want (ones that don't overflow the result) and you will see that the addition result does not know nor care about signed vs unsigned operants. Part of the beauty of twos complement.

Subtraction is just addition in logic, some may have learned "invert and add one" want to know what the bit pattern 11111111 is you invert 00000000 and add 1 00000001 so 11111111 is -1. But how does addition really work with two operands as shown above you really need a three bit adder three bits in and two bits out the result and carry out, so there is a carry in, two operand bits a result and carry out. What if we go back to grade school as well...

-32 - 3 = (-32) + (-3) apply the invert and add one to the -3 and we get (-32) + (~3) + 1

           1
    11100000
+   11111100
==============

and thats how a computer does that math, inverts the carry in and the second operand. SOME invert the carry out because a 1 on carry out when the adder is used as a subtractor means no borrow, but a 0 means a borrow happened. so some instruction sets will invert the carry out some will not. this is hugely important for this topic.

Likewise the carry out bit is computed based on the addition of the msbits of the operands and the carry in to that position, it is the carry out of that addition.

 abcxxxxxx
  dxxxxxxx
+ exxxxxxx
============
  f 

a the carry out is the carry out when adding bits b+d+e. This is also known as the unsigned overflow flag when this is an addition operation and the operands are considered to be unsigned values. But the signed overflow flag is determined by whether b and a are equal or not equal.

In what situations does this happen.

bde af
000 00
001 01
010 01
011 10 <--
100 01 <--
101 10
110 10
111 11

so you can read that is carry in is not equal to carry out for the msbit there is a signed overflow. At the same time you can say if the msbit of the operands are equal and the msbit of the result is not equal to those operand bits then signed overflow is true. If you generate a table of signed numbers and their results and which overflow this will start to be clear, you don't have to do 8 bit by 8 bit 256 * 256 combinations, take 3 or 4 bit numbers synthesize your own addition routines that or 3 or 4 bits and that smaller number of combinations will be enough.

So while addition and subtraction themselves as far as the result bits go do not know signed from unsigned the flags if you have a processor that uses them the C or carry flag the V or overflow flag have a signed based use case. The carry flag itself can have of two definitions when produced by a subtract depending on the instruction set and since comparisons are generally done with a subtraction that carry definition matters to how the flags are then used.

Greater than or less than while using a subtract to determine how they are used and the result itself is not affected by signedness how the flags are interpreted very much are.

Take some four bit positive numbers.

1101 - 1100 (13 - 12)
1100 - 1100 (12 - 12)
1011 - 1100 (11 - 12)

 11111
  1101
+ 0011
=======
  0001
carry out 1, zero flag 0, v = 0, n = 0

 11111
  1100
+ 0011
========
  0000
carry out 1, zero flag 1, v = 0, n = 0

 00111
  1011
+ 0011
========
  1111
carry out 0, zero flag 0, v = 0, n = 1

(n is the msbit of the result, the sign bit 1 means signed negative number, zero means signed positive number)

cz
10 greater than but not equal
11 equal
00 less than but not equal

same bit patterns

1101 - 1100 (-3 - -4)
1100 - 1100 (-4 - -4)
1011 - 1100 (-5 - -4)

cz
10 greater than but not equal
11 equal
00 less than but not equal

so far nothing changed.

but if I examine all the combinations

#include <stdio.h>
int main ( void )
{
    unsigned int ra;
    unsigned int rb;
    unsigned int rc;
    unsigned int rx;
    unsigned int v;
    unsigned int n;

    int sa,sb;

    for(ra=0;ra<0x10;ra++)
    for(rb=0;rb<0x10;rb++)
    {
        for(rx=8;rx;rx>>=1) if(rx&ra) printf("1"); else printf("0");
        printf(" - ");
        for(rx=8;rx;rx>>=1) if(rx&rb) printf("1"); else printf("0");
        rc=ra-rb;
        printf(" = ");
        for(rx=8;rx;rx>>=1) if(rx&rb) printf("1"); else printf("0");
        printf(" c=%u",(rc>>4)&1);
        printf(" n=%u",(rc>>3)&1);
        n=(rc>>3)&1;
        if((rc&0xF)==0) printf(" z=1"); else printf(" z=0");
        v=0;
        if((ra&8)==(rb&8))
        {
            if((ra&8)==(rc&8)) v=1;
        }
        printf(" v=%u",v);
        printf(" (%2u - %2u)",ra,rb);
        sa=ra;
        if(sa&8) sa|=0xFFFFFFF0;
        sb=rb;
        if(sb&8) sb|=0xFFFFFFF0;
        printf(" (%+2d - %+2d)",sa,sb);

        if(rc&0x10) printf(" C ");
        if(n==v) printf(" NV ");
        printf("\n");
    }
}

you can find fragments within the output that show the problem.

0000 - 0110 = 0110 c=1 n=1 z=0 v=0 ( 0 -  6) (+0 - +6) C 
0000 - 0111 = 0111 c=1 n=1 z=0 v=0 ( 0 -  7) (+0 - +7) C 
0000 - 1000 = 1000 c=1 n=1 z=0 v=0 ( 0 -  8) (+0 - -8) C 
0000 - 1001 = 1001 c=1 n=0 z=0 v=0 ( 0 -  9) (+0 - -7) C  NV 
0000 - 1010 = 1010 c=1 n=0 z=0 v=0 ( 0 - 10) (+0 - -6) C  NV 
0000 - 1011 = 1011 c=1 n=0 z=0 v=0 ( 0 - 11) (+0 - -5) C  NV 

For unsigned 0 is less than 6,7,8,9... so the carry out is set so that means greater than. But the same bit patterns signed 0 is less than 6 and 7 but greater than -8 -7 -6 ...

What is not obvious necessarily until you stare at it a lot or just cheat and look at ARMs documentation for signed if N == V it is a signed greater than or equal. for N != V it is a signed less than. don't need to examine the carry out. particularly the signed bit pattern problems 0000 and 1000 don't work with the carry like other bit patterns.

Hmm, I wrote this all up in other questions before. Anyway, multiply both does and doesn't care about unsigned and signed.

Using your calculator 0xF * 0xF = 0xE1. The biggest 4 bit number times the biggest 4 bit number gives an 8 bit number, we need twice as many bits to cover all the bit patterns.

        1111
*       1111
=================    
        1111
       1111
      1111
+    1111
=================
    11100001

so we see the addition that results is at least 2n-1 bits, if you end up with a carry off that last bit then you end up with 2n bits.

but, what is -1 * -1? its equal to 1 right? what are we missing?

unsigned has implied zeros

    00001111
*       1111
=================    
    00001111
   00001111
  00001111
+00001111
=================
 00011100001

but signed the sign is extended

    11111111
*       1111
=================    
    11111111
   11111111
  11111111
+11111111
=================
 00000000001

so sign matters with multiply?

0xC * 0x3 = 0xF4 or 0x24.

#include <stdio.h>
int main ( void )
{
    unsigned int ra;
    unsigned int rb;
    unsigned int rc;
    unsigned int rx;
    int sa;
    int sb;
    int sc;

    for(ra=0;ra<0x10;ra++)
    for(rb=0;rb<0x10;rb++)
    {
        sa=ra;
        if(ra&8) sa|=0xFFFFFFF0;
        sb=rb;
        if(rb&8) sb|=0xFFFFFFF0;
        rc=ra*rb;
        sc=sa*sb;
        if((rc&0xF)!=(sc&0xF))
        {
            for(rx=8;rx;rx>>1) if(rx&ra) printf("1"); else printf("0");
            printf(" ");
            for(rx=8;rx;rx>>1) if(rx&rb) printf("1"); else printf("0");
            printf("\n");
        }
    }
}

and there is no output. as expected. the bits abcd * 1111

        abcd
        1111
===============
    aaaaabcd
   aaaaabcd
  aaaaabcd
 aaaaabcd
================

four bits in on each operand if I only care about the lower four bits out

        abcd
        1111
===============
        abcd
        bcd
        cd
        d
================

how the operand sign extends does not matter as far as the result is concerned

Now knowing that a significant portion of the possible combinations of n bit times n bit equals n bit overflow it doesnt help you much to do such a thing in any code you want to be useful.

int a,b,c;
c = a * b;

not very useful except for smaller numbers.

But the reality is as far as multiply if the result is the same size as the operands then signed vs unsigned does not matter, if the result is the proper twice the size of the operands then you need a separate signed multiply instruction/operation and an unsigned. You can certainly cascade/synthesize the nn=2n with an nn=n instruction as you will see in some instruction sets.

bitwise operands, xor, or, and, these are bitwise they dont/cant care about sign.

shift left start with abcd shift one bcd0, shift two cd00 and so on. not very interesting. Shift right though desires to have separate arithmetic and logical shift right where arithmetic the msbit is duplicated as the shift in bit, and logical a zero shifts in arithmetic abcd aabc aaab aaaa, logical abcd 0abc 00ab 000a 0000

But we dont have two kinds of shift right in C. But when doing addition and subtraction directly, bits is bits, the beauty of twos complement. When doing a comparison which is a subtract then the flags used are different for signed vs unsigned for a number of the comparisons, get the older ARM architectural reference manual, I think they call it the armv5 one, even though it goes back to the armv4 and up to the armv6.

There is a section called "The condition field" and a table, this very nicely shows at least for the ARM flags the flag combinations for both unsigned this and that, signed this and that and the ones that dont care about signedness (equal, not equal, etc) wont say anything.

Understand/remember that some instruction sets not only invert the carry in bit and second operand on a subtract but will also invert the carry out bit. so if a carry bit is used on something signed then it is inverted. the stuff I did above where I tried to use the term carry out instead of carry flag, the carry flag would be inverted for some other instruction sets and the unsigned greater than and less than table flips over.

Division is not as easy to show, you have to do long division, etc. I will leave that one to the reader.

Not all documentation is as good as the table I am referring to in ARMs docs. Other processor documentation may or may not make the unsigned vs signed, they might just say jump if greater than and you may have to experimentally figure out what that means. Now that you now all of this you may have already figured out you dont for example need a branch if unsigned or equal. That just means branch if not less than so you can

cmp r0,r1
or 
cmp r1,r0

and just use branch if carry to cover the unsigned less than, unsigned less than or equal, unsigned greater than, unsigned greater than or equal cases. Although you might upset some programmers doing that because you were trying to save some bits in the instruction.

Saying ALL of that, the processor never distinguishes signed from unsigned. These are concepts that only mean something to the programmer, processors are very stupid. Bits is bits, the processor doesnt know if these bits are an address, if they are a variable if they are a character in a string, a floating point number (being implemented with a soft float library in fixed point), these interpretations are only meaningful to the programmer not the processor. The processor does not "distinguish between unsigned and signed in machine code", the programmer has to properly place bits that are meaningful to the programmer and then select the right instructions and sequences of instructions to perform the task the programmer wants performed. Some 32 bit number in a register is only an address when those bits are used to address something with a load or store, once that one clock cycle where they are sampled to be delivered to an address bus they are an address, before and after that they are just bits. When you increment that pointer in your program they are not an address they are just bits you are adding some other bits to. You can certainly build a MIPS like instruction set with no flags, and only N bit to N bit multiplies, only have a jump if two registers are equal or not equal instruction no other greater than or less than type instructions and still be able to make useful programs just like instruction sets that go overboard with those things unsigned this flag and signed that flag, unsigned this instruction and signed that instruction.

A not so popular but sometimes talked about in school, maybe there was a real instruction set or many that did this is a non-twos complement solution and that pretty much means sign and magnitude a sign bit and an unsigned value so +3 is 0011 and -3 is 1011 for a four bit register that burns one bit for sign when doing signed math. You then as with twos complement have to sit down with pencil and paper and work through the math operations, grade school style, then implement those in logic. Does this result in a separate unsigned and signed add? twos complement 4 bit registers we can do 0-15 and -8 to +7 for sign magnitude we can declare unsigned is 0 - 15 but signed is -7 to +7. An exercise for the reader, the question/quote had to do with twos complement.

Dip Hasan
  • 225
  • 3
  • 9
old_timer
  • 69,149
  • 8
  • 89
  • 168
1

Check out Two's Complement and its arithmetic operations, it is signed numbers in binary.

Two's complement is the most common method of representing signed integers on computers. In this scheme, if the binary number 010(2) encodes the signed integer 2(10), then its two's complement, 110(2), encodes the inverse: -2(10). In other words, to reverse the sign of any integer in this scheme, you can take the two's complement of its binary representation.

That way it is possible to have arithmetic operations between positive and negative binary values.

Two's Complement Python code snippet:

def twos_complement(input_value, num_bits):
    '''Calculates a two's complement integer from the given input value's bits'''
    mask = 2**(num_bits - 1)
    return -(input_value & mask) + (input_value & ~mask)
solarblue
  • 31
  • 6