8

I have homework to write assembly code for checking if number is odd or even. I have this code

code_seg SEGMENT
    ASSUME cs:code_seg, ds:data_seg;

    mov ax, 11;
    test ax, 1;

end: jmp end;
code_seg ENDS

And to check if number is even I look if zero flag is set. I know that the test instruction is like logical AND and that if result is 0 it sets zero flag. My question is: how this checks if number is odd/even? Actually I can't figure out why some even (binary) number and (logical and) 1 gives result of 0?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
vm381
  • 119
  • 1
  • 1
  • 5
  • 2
    A number is even if the least significant bit is zero. `0 & 1 = 0`, `1 & 1 = 1`. – Jester Mar 05 '18 at 18:07
  • I guess it is x86-assembly? `test ax, 1` is, as you already figured out, equal to `and ax, 1`, but does not change the value of `ax`, only flags. as @Jester said:a is even <=> ( a & 1 ) == 0. so all you have to do now is something conditional, e.g. `jz isEven`, `jnz isNotEven`, `setz dx` and you are done. You could e.g. print »number is even« or »number is not even« on the commandline output. – sivizius Mar 05 '18 at 18:27
  • 2
    It's not *logical* `and` as `&&` in C, but *bitwise* `and` as `&` in C. So `test ax,1` will be non-zero if and only if the least significant bit in `ax` is set to 1. And that one, when interpreted as part of integer value, is used as zeroth power of two, i.e. +1 to total integer value. It's the only one which is not divisible by two (other powers of two are). See AJNeufeld answer, which is quite good IMO (except that missing bit about `&&` vs `&`). – Ped7g Mar 05 '18 at 18:27

4 Answers4

9

Both unsigned and signed numbers (Two's complement) are odd if the lowest bit is set:

00000001 = 1    (odd)    // unsigned, or signed positive
11111111 = 255  (odd)    // unsigned
01111111 = 127  (odd)    // unsigned, or signed positive
10000001 = -127 (odd)    // signed negative
11111111 = -1   (odd)    // signed negative

So the test instruction

test al, 1

checks if the lowest bit of AL/AX/EAX/RAX is set. If it is, the number is odd.
This can be checked using the Jcc instructions, especially those testing the ?ZERO flag with

JNZ target    ; jump if odd  = lowest bit set
JZ  target    ; jump if even = lowest bit clear = zero
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
zx485
  • 28,498
  • 28
  • 50
  • 59
  • `test al,1` is smaller and just as efficient. (Fun fact: there's no encoding for `test r/m32, sign_extended_imm8`, so `test eax, 1` wastes multiple bytes). – Peter Cordes Oct 28 '19 at 02:36
3

A (small) integer can be expressed in binary as b3 b2 b1 b0:

b3 * 2^3  +  b2 * 2^2  +  b1 * 2^1  +  b0 * 2^0 =
b3 *  8   +  b2 *  4   +  b1 *  2   +  b0 *  1

where all bn values are either zero or one.

The only way to get an odd number is if the least significant bit (b0) is 1.

AND’ing a value with 1 (in binary, 0001) masks off all bits except the least significant bit (b0) ...

            b3  b2  b1  b0
binary-AND   0   0   0   1
            --  --  --  --
             0   0   0  b0

... giving a zero value if the least significant bit was zero (an even number), or a non-zero value (specifically 1) if the least significant bit was one (an odd number).

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
-1

Use a SHIFT operation to set a carry flag equal to the least significant bit. If the carry is 1, the number is odd.

For little-ending: Shift right, check carry

For big-ending: Shift left, check carry

As far as I know this works on any binary CPU but some CPUs are overly complicated with shift operations and carry flags so you must be sure you are only shifting a single bit one time to a single 1-bit register

  • Endianness is about what happens if you store a word and then reload one of its bytes using a byte load. i.e. byte layout of a word *in memory*. It's *not* about bit layout within bytes. And in any case, registers don't have really endianness (or a memory-destination shift works the same as if you'd loaded into a register, shifted, then stored the shift result). **Right shift *always* shifts out the least-significant bit.** Also, the code already shows a more efficient and non-destructive way to test, `x & 1`, and is asking how that works. Not for alternatives. – Peter Cordes Sep 26 '21 at 21:09
  • Also, no, shifting into a carry flag doesn't work on *every* CPU. Some, like MIPS, don't have flags at all. On MIPS, you'd use `andi $t0, $a0, 1` / `beq $zero, $t0, low_bit_zero`, or `sll $t0, $a0, 31` / `bgez $t0, low_bit_zero`. (Shift the bit to the MSB position, then branch on the tmp register being >= 0 as a 2's complement signed integer.) – Peter Cordes Sep 26 '21 at 21:14
-2

sadly am not very experienced with assembly, but in pseudocode there is a feature called "mod" which gives the remainder of a division.

have a look here: Assembly Language - How to Do Modulo?

for example:

x = 3 z = x mod 2

z would be 1, and if x were equal, z would be 0

ba_D
  • 9
  • 1
  • 1
    Why would you do that, if the binary encoded number already contains all power-of-two modulos only one `and` away, just like the OP does in the question? The `mod` or `div` operation is quite costly, not that bad today, but still if you already have the value, you should not to calculate it again. (I'm tempted to downvote, but your answer may be useful when one wants modulo by non-power-of-two value). – Ped7g Mar 05 '18 at 18:26
  • 1
    Testing / extracting the low bit is the optimal way to do modulo 2 in assembly language. This answer sort of implies using the `div` instruction, which would be a *terrible* choice, taking more instructions and running many times slower. See the first line of my [Collatz conjecture optimization answer](https://stackoverflow.com/questions/40354978/why-is-this-c-code-faster-than-my-hand-written-assembly-for-testing-the-collat/40355466#40355466). – Peter Cordes Mar 05 '18 at 19:43