Add ADC instruction implementations
parent
16c275282e
commit
040133eb82
148
src/cpu.rs
148
src/cpu.rs
|
@ -64,6 +64,8 @@ impl Processor {
|
|||
dst_address: usize,
|
||||
value: u8,
|
||||
) -> Result<(), Error> {
|
||||
// This lint is technically correct but we know this is the only error that can really happen here
|
||||
#[allow(clippy::match_wildcard_for_single_variants)]
|
||||
self.memory
|
||||
.set(dst_address, value)
|
||||
.map(|_| ())
|
||||
|
@ -880,4 +882,150 @@ mod tests {
|
|||
(register::Flag::Carry, carry_flag),
|
||||
);
|
||||
}
|
||||
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0x17; "no carry to register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0x18; "carry to register b")]
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0x17; "no carry to register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0x18; "carry to register c")]
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0x17; "no carry to register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0x18; "carry to register d")]
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0x17; "no carry to register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0x18; "carry to register e")]
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0x17; "no carry to register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0x18; "carry to register h")]
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0x17; "no carry to register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0x18; "carry to register l")]
|
||||
fn test_add_with_carry_to_a_value(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_value: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_eq!(expected_value, processor.registers.a);
|
||||
}
|
||||
|
||||
// these parameters are like impossible to read but I can't think of a nicer way to do this right now
|
||||
// B register
|
||||
#[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0, 0, 0; "carry bit set, results in no flags, register b")]
|
||||
#[test_case(0x88, 0x00, 0, register::SingleEightBit::B, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register b")]
|
||||
#[test_case(0x88, 0x00, 1, register::SingleEightBit::B, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x1, 0, 1, 0; "no carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x1, 0, 1, 0; "carry bit, half carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x0F, 0, 1, 0; "no carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x0F, 0, 1, 0; "carry bit, full carry, register b")]
|
||||
#[test_case(0x88, 0xFF, 1, register::SingleEightBit::B, 0xFF, 0, 1, 1; "carry bit, both carries, register b")]
|
||||
// C register
|
||||
#[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0, 0, 0; "carry bit set, results in no flags, register c")]
|
||||
#[test_case(0x89, 0x00, 0, register::SingleEightBit::C, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register c")]
|
||||
#[test_case(0x89, 0x00, 1, register::SingleEightBit::C, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x1, 0, 1, 0; "no carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x1, 0, 1, 0; "carry bit, half carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x0F, 0, 1, 0; "no carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x0F, 0, 1, 0; "carry bit, full carry, register c")]
|
||||
#[test_case(0x89, 0xFF, 1, register::SingleEightBit::C, 0xFF, 0, 1, 1; "carry bit, both carries, register c")]
|
||||
// D register
|
||||
#[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0, 0, 0; "carry bit set, results in no flags, register d")]
|
||||
#[test_case(0x8A, 0x00, 0, register::SingleEightBit::D, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x00, 1, register::SingleEightBit::D, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x1, 0, 1, 0; "no carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x1, 0, 1, 0; "carry bit, half carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x0F, 0, 1, 0; "no carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x0F, 0, 1, 0; "carry bit, full carry, register d")]
|
||||
#[test_case(0x8A, 0xFF, 1, register::SingleEightBit::D, 0xFF, 0, 1, 1; "carry bit, both carries, register d")]
|
||||
// E register
|
||||
#[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0, 0, 0; "carry bit set, results in no flags, register e")]
|
||||
#[test_case(0x8B, 0x00, 0, register::SingleEightBit::E, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x00, 1, register::SingleEightBit::E, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x1, 0, 1, 0; "no carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x1, 0, 1, 0; "carry bit, half carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x0F, 0, 1, 0; "no carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x0F, 0, 1, 0; "carry bit, full carry, register e")]
|
||||
#[test_case(0x8B, 0xFF, 1, register::SingleEightBit::E, 0xFF, 0, 1, 1; "carry bit, both carries, register e")]
|
||||
// H register
|
||||
#[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0, 0, 0; "carry bit set, results in no flags, register h")]
|
||||
#[test_case(0x8C, 0x00, 0, register::SingleEightBit::H, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x00, 1, register::SingleEightBit::H, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x1, 0, 1, 0; "no carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x1, 0, 1, 0; "carry bit, half carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x0F, 0, 1, 0; "no carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x0F, 0, 1, 0; "carry bit, full carry, register h")]
|
||||
#[test_case(0x8C, 0xFF, 1, register::SingleEightBit::H, 0xFF, 0, 1, 1; "carry bit, both carries, register h")]
|
||||
// L register
|
||||
#[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0, 0, 0; "carry bit set, results in no flags, register l")]
|
||||
#[test_case(0x8D, 0x00, 0, register::SingleEightBit::L, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x00, 1, register::SingleEightBit::L, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x1, 0, 1, 0; "no carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x1, 0, 1, 0; "carry bit, half carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x0F, 0, 1, 0; "no carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x0F, 0, 1, 0; "carry bit, full carry, register l")]
|
||||
#[test_case(0x8D, 0xFF, 1, register::SingleEightBit::L, 0xFF, 0, 1, 1; "carry bit, both carries, register l")]
|
||||
fn test_add_with_carry_to_a_flags(
|
||||
opcode: u8,
|
||||
initial_value: u8,
|
||||
carry_bit: u8,
|
||||
operand_register: register::SingleEightBit,
|
||||
operand: u8,
|
||||
expected_zero_flag: u8,
|
||||
expected_half_carry_flag: u8,
|
||||
expected_carry_flag: u8,
|
||||
) {
|
||||
let mut processor = Processor::default();
|
||||
processor.registers.a = initial_value;
|
||||
processor
|
||||
.registers
|
||||
.set_single_8bit_register(operand_register, operand);
|
||||
|
||||
// Set all the register to the opposite we expect to ensure they all get set
|
||||
set_opposite_of_expected_flags(
|
||||
&mut processor,
|
||||
(
|
||||
expected_zero_flag,
|
||||
0,
|
||||
expected_half_carry_flag,
|
||||
expected_carry_flag,
|
||||
),
|
||||
);
|
||||
|
||||
// ...except for the carry bit, which we must set for the test
|
||||
processor
|
||||
.registers
|
||||
.set_flag_bit(register::Flag::Carry, carry_bit);
|
||||
|
||||
let data = [opcode, 0x01];
|
||||
let (ins, extra_data) =
|
||||
RunnableInstruction::from_data(&data).expect("could not parse instruction");
|
||||
assert_eq!(extra_data, &[0x01]);
|
||||
processor.run(&ins);
|
||||
|
||||
assert_flags_eq!(
|
||||
processor,
|
||||
(register::Flag::Zero, expected_zero_flag),
|
||||
(register::Flag::Subtract, 0),
|
||||
(register::Flag::HalfCarry, expected_half_carry_flag),
|
||||
(register::Flag::Carry, expected_carry_flag),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,5 @@ pub enum EightBitArithmeticInstruction {
|
|||
AddSingleRegisterToA { src: register::SingleEightBit },
|
||||
AddImmediateToA { n: u8 },
|
||||
AddHLAddressToA,
|
||||
AddSingleRegisterToAWithCarry { src: register::SingleEightBit },
|
||||
}
|
||||
|
|
|
@ -21,6 +21,12 @@ impl OpcodeParser for EightBitAddParser {
|
|||
0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data),
|
||||
0x86 => build_add_hl_address_to_a_data(data),
|
||||
0xC6 => build_add_immediate_to_a_data(data),
|
||||
0x88 => build_add_register_to_a_with_carry_data(register::SingleEightBit::B, data),
|
||||
0x89 => build_add_register_to_a_with_carry_data(register::SingleEightBit::C, data),
|
||||
0x8A => build_add_register_to_a_with_carry_data(register::SingleEightBit::D, data),
|
||||
0x8B => build_add_register_to_a_with_carry_data(register::SingleEightBit::E, data),
|
||||
0x8C => build_add_register_to_a_with_carry_data(register::SingleEightBit::H, data),
|
||||
0x8D => build_add_register_to_a_with_carry_data(register::SingleEightBit::L, data),
|
||||
_ => Err(Error::UnknownOpcode(opcode)),
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +48,25 @@ fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> P
|
|||
.ok_or(Error::NoData)
|
||||
}
|
||||
|
||||
fn build_add_register_to_a_with_carry_data(
|
||||
src: register::SingleEightBit,
|
||||
data: &[u8],
|
||||
) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
(
|
||||
RunnableInstruction {
|
||||
instruction: Instruction::EightBitArithmetic(
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src },
|
||||
),
|
||||
cycles: 4,
|
||||
},
|
||||
remaining_data,
|
||||
)
|
||||
})
|
||||
.ok_or(Error::NoData)
|
||||
}
|
||||
|
||||
fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult {
|
||||
data.get(1..)
|
||||
.map(|remaining_data| {
|
||||
|
|
|
@ -19,6 +19,9 @@ pub enum Error {
|
|||
/// An invalid address was stored in the given register and attempted to be loaded to/from
|
||||
#[error("invalid address stored in register ({0}): {1:X}")]
|
||||
InvalidRegisterAddress(register::SixteenBit, usize),
|
||||
/// An invalid address was stored in the given register and attempted to be loaded to/from
|
||||
#[error("invalid value stored in carry flag: {0:X}")]
|
||||
InvalidCarryFlagValue(u8),
|
||||
#[error("an unknown error occured: {0}")]
|
||||
Unknown(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error, Processor};
|
||||
use crate::{memory, register};
|
||||
|
||||
use super::arithutil;
|
||||
use super::{arithutil::CarryingAdd, InstructionRunner};
|
||||
|
||||
pub(super) struct EightBitArithmeticRunner;
|
||||
|
@ -76,6 +77,26 @@ impl InstructionRunner<EightBitArithmeticInstruction> for EightBitArithmeticRunn
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src } => {
|
||||
let src_register_value = processor.registers.get_single_8bit_register(src);
|
||||
|
||||
let carried_operand = arithutil::CarriedNumber::new(
|
||||
src_register_value,
|
||||
processor.registers.get_flag_bit(register::Flag::Carry),
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
arithutil::Error::InvalidCarryBit(value) => Error::InvalidCarryFlagValue(value),
|
||||
})?;
|
||||
|
||||
let (result, half_carry, carry) =
|
||||
processor.registers.a.add_with_carry(carried_operand);
|
||||
|
||||
processor.registers.a = result;
|
||||
set_addition_flags(processor, result, half_carry, carry);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,57 @@
|
|||
//! arithutil contains utilities to generalize the implementation of arithmetic
|
||||
//! arithutil contains utilities to generalize the implementation of arithmetic instructions
|
||||
|
||||
use std::{marker::PhantomData, ops::Add};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use self::upgrade::UpgradableNumber;
|
||||
mod upgrade;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("invalid carry bit value {0}. Must be zero or one.")]
|
||||
InvalidCarryBit(u8),
|
||||
}
|
||||
|
||||
/// `CarriedNumber` is a number combined with its carry bit, intended to be used to perform carry-based arithmetic
|
||||
/// operations such as ADC.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CarriedNumber<N, V> {
|
||||
num: N,
|
||||
carry_bit: u8,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<N, V> CarriedNumber<N, V>
|
||||
where
|
||||
N: Copy + Clone + PartialEq + UpgradableNumber<V>,
|
||||
V: From<N> + From<u8> + Add<Output = V>,
|
||||
{
|
||||
/// Create a new [`CarriedNumber`] with a raw value and its carry bit. Note that the carry bit must be 0 or 1,
|
||||
/// or an [`Error::InvalidCarryBit`] will be returned
|
||||
pub fn new(num: N, carry_bit: u8) -> Result<Self, Error> {
|
||||
if carry_bit > 1 {
|
||||
return Err(Error::InvalidCarryBit(carry_bit));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
num,
|
||||
carry_bit,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the combination between the value and the carry bit. The return value is the "upgraded" version of `N`.
|
||||
/// See [`upgrade::UpgradableNumber`] for more details
|
||||
pub fn value(self) -> V {
|
||||
self.num.upgrade() + V::from(self.carry_bit)
|
||||
}
|
||||
|
||||
/// Get the number and its carry bit directly
|
||||
pub fn raw_values(self) -> (N, u8) {
|
||||
(self.num, self.carry_bit)
|
||||
}
|
||||
}
|
||||
|
||||
/// `CarryingAdd` describes a type that can perform addition to produce the carry flags needed for the operation
|
||||
/// of the gameboy
|
||||
|
@ -44,12 +97,38 @@ impl CarryingAdd<i8, u16> for u16 {
|
|||
}
|
||||
}
|
||||
|
||||
impl CarryingAdd<CarriedNumber<u8, u16>, u8> for u8 {
|
||||
fn add_with_carry(self, rhs: CarriedNumber<u8, u16>) -> (u8, bool, bool) {
|
||||
let total = rhs.value() + u16::from(self);
|
||||
// Given this is adding two u8s, the largest value we can produce is 0x01FF (both operands 0xFF, plus a carry bit)
|
||||
// which can never wrap, so we just take the lower byte to convert back to u8
|
||||
let [_, smallsized_total] = total.to_be_bytes();
|
||||
|
||||
(
|
||||
smallsized_total,
|
||||
did_8bit_half_carry_including_carry_bit(self, rhs.num, rhs.carry_bit),
|
||||
did_8bit_full_carry_including_carry_bit(self, rhs.num, rhs.carry_bit),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// `did_8_bit_half_carry` checks whether or not the given addition would have done an 8 bit half carry.
|
||||
// NOTE: These generics are not as generic as they could be. The Into<u16> is just a shortcut because we
|
||||
// only use up to u16s
|
||||
fn did_8bit_half_carry<L: Into<u16>, R: Into<u16>>(lhs: L, rhs: R) -> bool {
|
||||
did_8bit_half_carry_including_carry_bit(lhs, rhs, 0)
|
||||
}
|
||||
|
||||
fn did_8bit_half_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
|
||||
lhs: L,
|
||||
rhs: R,
|
||||
carry_bit: u8,
|
||||
) -> bool {
|
||||
// This should be validated far before use
|
||||
assert!(carry_bit <= 1, "carry bit must be zero or one");
|
||||
|
||||
// https://stackoverflow.com/a/7261149/1103734
|
||||
(lhs.into() & 0xf) + (rhs.into() & 0xf) > 0xf
|
||||
(lhs.into() & 0xf) + (rhs.into() & 0xf) + u16::from(carry_bit) > 0xf
|
||||
}
|
||||
|
||||
/// `did_8_bit_full_carry` checks whether or not the given addition would have done an 8 bit carry.
|
||||
|
@ -60,6 +139,18 @@ fn did_8bit_full_carry<L: Into<u16>, R: Into<u16>>(lhs: L, rhs: R) -> bool {
|
|||
((lhs.into() & 0xff) + (rhs.into() & 0xff)) > 0xff
|
||||
}
|
||||
|
||||
fn did_8bit_full_carry_including_carry_bit<L: Into<u16>, R: Into<u16>>(
|
||||
lhs: L,
|
||||
rhs: R,
|
||||
carry_bit: u8,
|
||||
) -> bool {
|
||||
// This should be validated far before use
|
||||
assert!(carry_bit <= 1, "carry bit must be zero or one");
|
||||
|
||||
// https://stackoverflow.com/a/7261149/1103734
|
||||
((lhs.into() & 0xff) + (rhs.into() & 0xff) + u16::from(carry_bit)) > 0xff
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -68,6 +159,7 @@ mod tests {
|
|||
#[test_case(0x00, 0x00, 0, false, false; "no addition")]
|
||||
#[test_case(0xFF, 0x10, 15, false, true; "overflow")]
|
||||
#[test_case(0xC0, 0x7F, 63, false, true; "overflow 2")]
|
||||
#[test_case(0xFE, 0x01, 0xFF, false, false; "hits max, no overflow")]
|
||||
#[test_case(0xFF, 0x01, 0, true, true; "overflow with half carry")]
|
||||
#[test_case(0x0A, 0x0C, 22, true, false; "half carry")]
|
||||
fn test_u8_carrying_add(a: u8, b: u8, expected: u8, half_carry: bool, full_carry: bool) {
|
||||
|
@ -110,4 +202,39 @@ mod tests {
|
|||
assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_carried_number() {
|
||||
let n = CarriedNumber::new(10, 1).expect("failed to construct CarriedNumber");
|
||||
assert_eq!((10, 1), n.raw_values());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_construct_carried_number_with_bad_carry_bit() {
|
||||
assert!(CarriedNumber::new(10, 20).is_err());
|
||||
}
|
||||
|
||||
#[test_case(0x12, 1, 0x13)]
|
||||
#[test_case(0x12, 0, 0x12)]
|
||||
#[test_case(0xFF, 1, 0x100)]
|
||||
#[test_case(0xFF, 0, 0xFF)]
|
||||
fn test_get_value_from_carried_number(value: u8, carry_bit: u8, expected_value: u16) {
|
||||
let carried_number = CarriedNumber::new(value, carry_bit)
|
||||
.expect("should have been able construct CarriedNumber");
|
||||
assert_eq!(expected_value, carried_number.value());
|
||||
}
|
||||
|
||||
#[test_case(0x12, CarriedNumber::new(0x01, 1).unwrap(), (0x14, false, false))]
|
||||
#[test_case(0x80, CarriedNumber::new(0x81, 1).unwrap(), (0x02, false, true))]
|
||||
#[test_case(0xFD, CarriedNumber::new(0x01, 1).unwrap(), (0xFF, false, false))]
|
||||
#[test_case(0x7F, CarriedNumber::new(0x01, 1).unwrap(), (0x81, true, false))]
|
||||
#[test_case(0xFF, CarriedNumber::new(0x01, 1).unwrap(), (0x01, true, true))]
|
||||
#[test_case(0xFF, CarriedNumber::new(0xFF, 1).unwrap(), (0xFF, true, true))]
|
||||
fn test_add_carried_number_to_u8(
|
||||
value: u8,
|
||||
carried_number: CarriedNumber<u8, u16>,
|
||||
expected: (u8, bool, bool),
|
||||
) {
|
||||
assert_eq!(expected, value.add_with_carry(carried_number));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/// `UpgradableNumber` represents an number that can be "upgraded" to its next largest type.
|
||||
/// This is slightly different than `From`, in that it only allows you to upgrade "one step up".
|
||||
/// You cannot, for instance, upgrade a `u8` to a `u64` directly.
|
||||
pub trait UpgradableNumber<U: From<Self>>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn upgrade(self) -> U {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradableNumber<u16> for u8 {}
|
||||
impl UpgradableNumber<u32> for u16 {}
|
||||
impl UpgradableNumber<u64> for u32 {}
|
||||
|
||||
impl UpgradableNumber<i16> for i8 {}
|
||||
impl UpgradableNumber<i32> for i16 {}
|
||||
impl UpgradableNumber<i64> for i32 {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
macro_rules! upgrade_test {
|
||||
($test_name: ident, $input_type:ty, $output_type:ty, $value:expr) => {
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
let input: $input_type = $value;
|
||||
let expected: $output_type = $value.into();
|
||||
|
||||
assert_eq!(expected, input.upgrade());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
upgrade_test!(upgrade_u8_to_u16, u8, u16, 64_u8);
|
||||
upgrade_test!(upgrade_u8_max_to_u16, u8, u16, u8::MAX);
|
||||
|
||||
upgrade_test!(upgrade_u16_u32, u16, u32, 325_u16);
|
||||
upgrade_test!(upgrade_u16_max_to_u32, u16, u32, u16::MAX);
|
||||
|
||||
upgrade_test!(upgrade_u32_u32, u32, u64, 2_u32.pow(26));
|
||||
upgrade_test!(upgrade_u32_max_to_u32, u32, u64, u32::MAX);
|
||||
|
||||
upgrade_test!(upgrade_i8_to_i16, i8, i16, 64_i8);
|
||||
upgrade_test!(upgrade_i8_max_to_i16, i8, i16, i8::MAX);
|
||||
|
||||
upgrade_test!(upgrade_i16_i32, i16, i32, 325_i16);
|
||||
upgrade_test!(upgrade_i16_max_to_i32, i16, i32, i16::MAX);
|
||||
|
||||
upgrade_test!(upgrade_i32_i32, i32, i64, 2_i32.pow(26));
|
||||
upgrade_test!(upgrade_i32_max_to_i32, i32, i64, i32::MAX);
|
||||
}
|
Loading…
Reference in New Issue