diff --git a/src/arm/Move.go b/src/arm/Move.go
index c687c2a..a0fc070 100644
--- a/src/arm/Move.go
+++ b/src/arm/Move.go
@@ -1,10 +1,73 @@
 package arm
 
 import (
+	"encoding/binary"
+
 	"git.urbach.dev/cli/q/src/cpu"
 	"git.urbach.dev/cli/q/src/sizeof"
 )
 
+// MoveRegisterNumber moves a number into the given register.
+func MoveRegisterNumber(code []byte, destination cpu.Register, number int) []byte {
+	instruction, encodable := MoveRegisterNumberSI(destination, number)
+
+	if encodable {
+		return binary.LittleEndian.AppendUint32(code, instruction)
+	}
+
+	return MoveRegisterNumberMI(code, destination, number)
+}
+
+// MoveRegisterNumberMI moves a number into the given register using movz and a series of movk instructions.
+func MoveRegisterNumberMI(code []byte, destination cpu.Register, number int) []byte {
+	movz := MoveZero(destination, 0, uint16(number))
+	code = binary.LittleEndian.AppendUint32(code, movz)
+	halfword := 1
+
+	for {
+		number >>= 16
+
+		if number == 0 {
+			return code
+		}
+
+		movk := MoveKeep(destination, halfword, uint16(number))
+		code = binary.LittleEndian.AppendUint32(code, movk)
+		halfword++
+	}
+}
+
+// MoveRegisterNumberSI moves a number into the given register using a single instruction.
+func MoveRegisterNumberSI(destination cpu.Register, number int) (uint32, bool) {
+	if sizeof.Signed(number) <= 2 {
+		if number < 0 {
+			return MoveInvertedWideImmediate(destination, uint16(^number), 0), true
+		}
+
+		return MoveZero(destination, 0, uint16(number)), true
+	}
+
+	if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 {
+		return MoveInvertedWideImmediate(destination, uint16((^number)>>48), 3), true
+	}
+
+	code, encodable := MoveBitmaskImmediate(destination, number)
+
+	if encodable {
+		return code, true
+	}
+
+	if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 {
+		return MoveInvertedWideImmediate(destination, uint16((^number)>>32), 2), true
+	}
+
+	if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 {
+		return MoveInvertedWideImmediate(destination, uint16((^number)>>16), 1), true
+	}
+
+	return 0, false
+}
+
 // MoveRegisterRegister copies a register to another register.
 func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32 {
 	if source == SP || destination == SP {
@@ -14,58 +77,22 @@ func MoveRegisterRegister(destination cpu.Register, source cpu.Register) uint32
 	return OrRegisterRegister(destination, ZR, source)
 }
 
-// MoveRegisterNumber moves a number into the given register.
-func MoveRegisterNumber(destination cpu.Register, number int) (uint32, bool) {
-	if number < 0 {
-		return MoveInvertedWideImmediate(destination, ^number, 0), true
-	}
-
-	if sizeof.Signed(number) <= 2 {
-		return MoveZero(destination, 0, uint16(number)), true
-	}
-
-	if (number&0xFFFFFFFFFFFF == 0xFFFFFFFFFFFF) && sizeof.Signed(number>>48) <= 2 {
-		return MoveInvertedWideImmediate(destination, (^number)>>48, 3), true
-	}
-
-	code, encodable := MoveBitmaskImmediate(destination, number)
-
-	if encodable {
-		return code, true
-	}
-
-	if (number&0xFFFFFFFF == 0xFFFFFFFF) && sizeof.Signed(number>>32) <= 2 {
-		return MoveInvertedWideImmediate(destination, (^number)>>32, 2), true
-	}
-
-	if (number&0xFFFF == 0xFFFF) && sizeof.Signed(number>>16) <= 2 {
-		return MoveInvertedWideImmediate(destination, (^number)>>16, 1), true
-	}
-
-	return 0, false
-}
-
 // MoveBitmaskImmediate moves a bitmask immediate value to a register.
 func MoveBitmaskImmediate(destination cpu.Register, number int) (uint32, bool) {
 	return OrRegisterNumber(destination, ZR, number)
 }
 
 // MoveInvertedWideImmediate moves an inverted 16-bit immediate value to a register.
-func MoveInvertedWideImmediate(destination cpu.Register, number int, shift uint32) uint32 {
+func MoveInvertedWideImmediate(destination cpu.Register, number uint16, shift uint32) uint32 {
 	return 0b100100101<<23 | shift<<21 | regImm(destination, number)
 }
 
 // MoveKeep moves a 16-bit integer into the given register and keeps all other bits.
 func MoveKeep(destination cpu.Register, halfword int, number uint16) uint32 {
-	return mov(0b11, halfword, number, destination)
+	return 0b111100101<<23 | regImmHw(destination, halfword, number)
 }
 
 // MoveZero moves a 16-bit integer into the given register and clears all other bits to zero.
 func MoveZero(destination cpu.Register, halfword int, number uint16) uint32 {
-	return mov(0b10, halfword, number, destination)
-}
-
-// mov encodes a generic move instruction.
-func mov(opCode uint32, halfword int, number uint16, destination cpu.Register) uint32 {
-	return 1<<31 | opCode<<29 | 0b100101<<23 | uint32(halfword<<21) | uint32(number<<5) | uint32(destination)
+	return 0b110100101<<23 | regImmHw(destination, halfword, number)
 }
diff --git a/src/arm/Move_test.go b/src/arm/Move_test.go
index 6e02a4e..af4c58e 100644
--- a/src/arm/Move_test.go
+++ b/src/arm/Move_test.go
@@ -30,7 +30,24 @@ func TestMoveRegisterRegister(t *testing.T) {
 func TestMoveRegisterNumber(t *testing.T) {
 	usagePatterns := []struct {
 		Register cpu.Register
-		Number   int
+		Number   uint64
+		Code     []byte
+	}{
+		{arm.X0, 0xCAFEBABE, []byte{0xC0, 0x57, 0x97, 0xD2, 0xC0, 0x5F, 0xB9, 0xF2}},
+		{arm.X0, 0xDEADC0DE, []byte{0xC0, 0x1B, 0x98, 0xD2, 0xA0, 0xD5, 0xBB, 0xF2}},
+	}
+
+	for _, pattern := range usagePatterns {
+		t.Logf("mov %s, 0x%X", pattern.Register, pattern.Number)
+		code := arm.MoveRegisterNumber(nil, pattern.Register, int(pattern.Number))
+		assert.DeepEqual(t, code, pattern.Code)
+	}
+}
+
+func TestMoveRegisterNumberSI(t *testing.T) {
+	usagePatterns := []struct {
+		Register cpu.Register
+		Number   uint64
 		Code     uint32
 	}{
 		// MOVZ
@@ -41,9 +58,10 @@ func TestMoveRegisterNumber(t *testing.T) {
 		{arm.X0, 0x1FFFF, 0xB24043E0},
 		{arm.X0, 0x7FFFFFFF, 0xB2407BE0},
 		{arm.X0, 0xFFFFFFFF, 0xB2407FE0},
+		{arm.X0, 0xC3FFFFFFC3FFFFFF, 0xB2026FE0},
 
 		// MOV (inverted wide immediate)
-		{arm.X0, -1, 0x92800000},
+		{arm.X0, 0xFFFFFFFFFFFFFFFF, 0x92800000},
 		{arm.X0, 0x7FFFFFFFFFFFFFFF, 0x92F00000},
 		{arm.X0, 0x2FFFFFFFF, 0x92DFFFA0}, // not encodable in the GNU assembler
 		{arm.X0, 0x2FFFF, 0x92BFFFA0},     // not encodable in the GNU assembler
@@ -55,7 +73,7 @@ func TestMoveRegisterNumber(t *testing.T) {
 
 	for _, pattern := range usagePatterns {
 		t.Logf("mov %s, %d", pattern.Register, pattern.Number)
-		code, encodable := arm.MoveRegisterNumber(pattern.Register, pattern.Number)
+		code, encodable := arm.MoveRegisterNumberSI(pattern.Register, int(pattern.Number))
 
 		if pattern.Code != 0 {
 			assert.True(t, encodable)
diff --git a/src/arm/encode.go b/src/arm/encode.go
index 69724a7..3e9aa4b 100644
--- a/src/arm/encode.go
+++ b/src/arm/encode.go
@@ -23,13 +23,14 @@ func pair(reg1 cpu.Register, reg2 cpu.Register, base cpu.Register, imm7 int) uin
 }
 
 // regImm encodes an instruction with a register and an immediate.
-func regImm(d cpu.Register, imm16 int) uint32 {
-	return uint32(imm16&mask16)<<5 | uint32(d)
+func regImm(d cpu.Register, imm16 uint16) uint32 {
+	return uint32(imm16)<<5 | uint32(d)
 }
 
-// reg2 encodes an instruction with 2 registers.
-func reg2(d cpu.Register, n cpu.Register) uint32 {
-	return uint32(n)<<5 | uint32(d)
+// regImmHw encodes an instruction with a register, an immediate and
+// the 2-bit halfword specifying which 16-bit region of the register is addressed.
+func regImmHw(d cpu.Register, hw int, imm16 uint16) uint32 {
+	return uint32(hw)<<21 | uint32(imm16)<<5 | uint32(d)
 }
 
 // reg2Imm encodes an instruction with 2 registers and an immediate.
diff --git a/src/asmc/compileARM.go b/src/asmc/compileARM.go
index a5b8e05..972d1e3 100644
--- a/src/asmc/compileARM.go
+++ b/src/asmc/compileARM.go
@@ -77,7 +77,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 
 			if operands.Address.OffsetRegister < 0 {
 				tmp := arm.X28
-				c.moveRegisterNumberARM(tmp, operands.Number)
+				c.code = arm.MoveRegisterNumber(c.code, tmp, operands.Number)
 				c.append(arm.StoreRegister(tmp, operands.Address.Base, int(operands.Address.Offset), operands.Address.Length))
 			} else {
 				panic("not implemented")
@@ -110,7 +110,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 				c.append(code)
 			} else {
 				tmp := arm.X28
-				c.moveRegisterNumberARM(tmp, operand.Number)
+				c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number)
 				c.append(arm.AndRegisterRegister(operand.Register, operand.Register, tmp))
 			}
 		case asm.TypeRegisterRegister:
@@ -128,7 +128,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 				c.append(code)
 			} else {
 				tmp := arm.X28
-				c.moveRegisterNumberARM(tmp, operand.Number)
+				c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number)
 				c.append(arm.OrRegisterRegister(operand.Register, operand.Register, tmp))
 			}
 		case asm.TypeRegisterRegister:
@@ -146,7 +146,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 				c.append(code)
 			} else {
 				tmp := arm.X28
-				c.moveRegisterNumberARM(tmp, operand.Number)
+				c.code = arm.MoveRegisterNumberMI(c.code, tmp, operand.Number)
 				c.append(arm.XorRegisterRegister(operand.Register, operand.Register, tmp))
 			}
 		case asm.TypeRegisterRegister:
@@ -201,7 +201,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 		case asm.TypeRegisterNumber:
 			operand := c.assembler.Param.RegisterNumber[x.Index]
 			tmp := arm.X28
-			c.moveRegisterNumberARM(tmp, operand.Number)
+			c.code = arm.MoveRegisterNumber(c.code, tmp, operand.Number)
 			c.append(arm.MulRegisterRegister(operand.Register, operand.Register, tmp))
 		}
 
@@ -227,7 +227,7 @@ func (c *compiler) compileARM(x asm.Instruction) {
 
 		case asm.TypeRegisterNumber:
 			operands := c.assembler.Param.RegisterNumber[x.Index]
-			c.moveRegisterNumberARM(operands.Register, operands.Number)
+			c.code = arm.MoveRegisterNumber(c.code, operands.Register, operands.Number)
 
 		case asm.TypeRegisterLabel:
 			operands := c.assembler.Param.RegisterLabel[x.Index]
diff --git a/src/asmc/movARM.go b/src/asmc/movARM.go
deleted file mode 100644
index e34fb0e..0000000
--- a/src/asmc/movARM.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package asmc
-
-import (
-	"git.urbach.dev/cli/q/src/arm"
-	"git.urbach.dev/cli/q/src/cpu"
-)
-
-func (c *compiler) moveRegisterNumberARM(register cpu.Register, number int) {
-	code, encodable := arm.MoveRegisterNumber(register, number)
-
-	if encodable {
-		c.append(code)
-	} else {
-		panic("not implemented") // movz movk
-	}
-}
diff --git a/src/sizeof/Unsigned.go b/src/sizeof/Unsigned.go
index 949b77a..b2df90a 100644
--- a/src/sizeof/Unsigned.go
+++ b/src/sizeof/Unsigned.go
@@ -3,15 +3,17 @@ package sizeof
 import "math"
 
 // Unsigned tells you how many bytes are needed to encode this unsigned number.
-func Unsigned(number uint64) int {
+func Unsigned[T uint | uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64](number T) int {
+	x := uint64(number)
+
 	switch {
-	case number <= math.MaxUint8:
+	case x <= math.MaxUint8:
 		return 1
 
-	case number <= math.MaxUint16:
+	case x <= math.MaxUint16:
 		return 2
 
-	case number <= math.MaxUint32:
+	case x <= math.MaxUint32:
 		return 4
 
 	default:
diff --git a/src/sizeof/Unsigned_test.go b/src/sizeof/Unsigned_test.go
index 94cdd10..b8c20f4 100644
--- a/src/sizeof/Unsigned_test.go
+++ b/src/sizeof/Unsigned_test.go
@@ -11,7 +11,9 @@ import (
 func TestUnsigned(t *testing.T) {
 	assert.Equal(t, sizeof.Unsigned(0), 1)
 	assert.Equal(t, sizeof.Unsigned(math.MaxUint8), 1)
+	assert.Equal(t, sizeof.Unsigned(math.MaxUint8+1), 2)
 	assert.Equal(t, sizeof.Unsigned(math.MaxUint16), 2)
+	assert.Equal(t, sizeof.Unsigned(math.MaxUint16+1), 4)
 	assert.Equal(t, sizeof.Unsigned(math.MaxUint32), 4)
-	assert.Equal(t, sizeof.Unsigned(math.MaxUint64), 8)
+	assert.Equal(t, sizeof.Unsigned(math.MaxUint32+1), 8)
 }