Store the annual nominal rate as a fraction, not a percentage

The field was called annual_rate_in_percent but nothing multiplied it by 100,
so a 3,41 % loan would have been stored as 3.41 and read as 341 %. Rename it
to annual_nominal_rate, which is what it holds, and add an explicit
annual_nominal_rate_in_percent() accessor for callers that want the display
form. Drop the trailing % from the error message, which was making the same
claim.

The nominal qualifier is there because TAEG will join this struct later and
the two must never be confused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Enguehard 2026-08-23 15:17:15 +02:00
parent 4506231f02
commit 0cf311bdf4

View file

@ -5,7 +5,7 @@ use crate::euros::Euros;
#[derive(Debug, PartialEq, Eq, Error, Clone)]
pub enum LoanOfferError {
#[error("loan cannot have a negative interest rate: {0}%")]
#[error("loan cannot have a negative interest rate: {0}")]
NegativeInterestRate(Decimal),
#[error("loan duration cannot be zero")]
ZeroLoanDuration,
@ -16,18 +16,18 @@ pub enum LoanOfferError {
#[derive(Debug, PartialEq, Eq)]
pub struct LoanOffer {
borrowed_capital: Euros,
annual_rate_in_percent: Decimal,
annual_nominal_rate: Decimal,
loan_duration_in_months: u64,
}
impl LoanOffer {
pub fn new(
borrowed_capital: Euros,
annual_rate_in_percent: Decimal,
annual_nominal_rate: Decimal,
loan_duration_in_months: u64,
) -> Result<Self, LoanOfferError> {
if annual_rate_in_percent < Decimal::ZERO {
Err(LoanOfferError::NegativeInterestRate(annual_rate_in_percent))
if annual_nominal_rate < Decimal::ZERO {
Err(LoanOfferError::NegativeInterestRate(annual_nominal_rate))
} else if loan_duration_in_months == 0 {
Err(LoanOfferError::ZeroLoanDuration)
} else if borrowed_capital <= Euros::from_cents_as_i64(0) {
@ -35,11 +35,15 @@ impl LoanOffer {
} else {
Ok(LoanOffer {
borrowed_capital,
annual_rate_in_percent,
annual_nominal_rate,
loan_duration_in_months,
})
}
}
pub fn annual_nominal_rate_in_percent(&self) -> Decimal {
self.annual_nominal_rate * Decimal::ONE_HUNDRED
}
}
#[cfg(test)]
@ -92,7 +96,7 @@ mod tests {
ret,
Ok(LoanOffer {
borrowed_capital: Euros::from_cents_as_i64(10),
annual_rate_in_percent: Decimal::new(45, 2),
annual_nominal_rate: Decimal::new(45, 2),
loan_duration_in_months: 15
})
);
@ -105,9 +109,20 @@ mod tests {
ret,
Ok(LoanOffer {
borrowed_capital: Euros::from_cents_as_i64(10),
annual_rate_in_percent: Decimal::ZERO,
annual_nominal_rate: Decimal::ZERO,
loan_duration_in_months: 15
})
);
}
#[test]
fn conversion_to_annual_nominal_rate_in_percent_works() {
let offer = LoanOffer::new(
Euros::from_cents_as_i64(150_000_00),
Decimal::new(4, 4),
300,
)
.unwrap();
assert_eq!(offer.annual_nominal_rate_in_percent(), Decimal::new(4, 2));
}
}