There are many theoretical ways to express an (arbitrarily complex) function in numbers, historically starting with the abstract model known as the Turing Machine. Those aren't always efficient for real problems, and in engineering we've settled on a pretty straightforward model.
Microprocessors (CPU's) have an associated memory which holds instructions. (1)
Instructions are discrete operations, which change the CPU is a well-defined way. There is a finite and numbered set of instructions. Instructions may have operands, such as registers to operate on, or constants to use, and these operands are also encoded as number.
The instruction pointer (or program counter) indicates where exactly in memory the next instruction is stored. It normally increments if an instruction finishes, but a jump or branch instruction can change it in different ways.
As instructions are stored as numbers, they must be decoded when the numbers are retrieved from memory. As you correctly assume, this is non-trivial. The instruction decoder will map an instruction number to a particular bit of hardware to implement the actual instruction.
Let's assume a new CPU design, not compatible with an existing CPU, to keep this simple. As a designer, we know have the freedom to choose which numbers are associated with which instructions. We're still talking CPU's, so a number is a bitstring. We can therefore decide that an instruction is a 32 bit number. We can say that the first bit decides whether the instruction is some type of jump instruction. If so, we send off the number bits to the hardware that changes the instruction pointer. Here we look at the 2nd bit to decide whether it's an unconditional jump. For simplicity, let's assume it is. The third bit may decide whether it's a branch (push IP on stack) or not. Decoding this bit is simple: if set, push. The 4th bit may indicate whether it's an absolute or relative jump. A relative jump would require us to use the arithmetic hardware of the CPU, so for simplicity assume we have an absolute jump. The 5th bit can decide whether to take the destination address from a register, or from an operand. Assuming we use an operand, we see that another number is needed from the instruction stream. This means we issue another read, using the incremented instruction
pointer (operand follows instruction). The fetched operand value becomes the new instruction pointer.
1) It's irrelevant here whether this memory is shared with data, or if it's augmented with caches.