Skip to main content

uk_paye/
lib.rs

1//! # uk-paye
2//!
3//! 2025/26 UK income tax (England, Wales, Northern Ireland) and National Insurance (Class 1
4//! employee), the core of a take-home calculation. Same figures behind the
5//! [UKMoneyCalc](https://ukmoneycalc.com/) calculators.
6//!
7//! ```
8//! use uk_paye::take_home;
9//! let (net, tax, ni) = take_home(50_000.0); // GBP gross
10//! assert!(net < 50_000.0 && tax > 0.0 && ni > 0.0);
11//! ```
12
13// 2025/26 thresholds (frozen).
14pub const PERSONAL_ALLOWANCE: f64 = 12_570.0;      // 0% band ceiling
15pub const BASIC_LIMIT: f64 = 50_270.0;             // 20% band ceiling
16pub const HIGHER_LIMIT: f64 = 125_140.0;           // 40% band ceiling
17pub const PA_TAPER_START: f64 = 100_000.0;         // allowance tapers above this
18
19// Class 1 employee NI 2025/26.
20pub const NI_PRIMARY_THRESHOLD: f64 = 12_570.0;
21pub const NI_UPPER_LIMIT: f64 = 50_270.0;
22pub const NI_MAIN_RATE: f64 = 0.08;                // 8% between PT and UEL
23pub const NI_UPPER_RATE: f64 = 0.02;               // 2% above UEL
24
25/// Personal allowance with the £100k taper (£1 lost per £2 over £100k).
26pub fn personal_allowance(gross: f64) -> f64 {
27    if gross <= PA_TAPER_START { return PERSONAL_ALLOWANCE; }
28    let reduced = PERSONAL_ALLOWANCE - (gross - PA_TAPER_START) / 2.0;
29    reduced.max(0.0)
30}
31
32/// UK income tax (excluding Scotland) for a gross salary.
33pub fn income_tax(gross: f64) -> f64 {
34    let pa = personal_allowance(gross);
35    let taxable = (gross - pa).max(0.0);
36    if taxable <= 0.0 { return 0.0; }
37    let basic_width = (BASIC_LIMIT - PERSONAL_ALLOWANCE).max(0.0);
38    let higher_width = (HIGHER_LIMIT - BASIC_LIMIT).max(0.0);
39    let mut tax = 0.0;
40    let mut remaining = taxable;
41    // 20% band
42    let b = remaining.min(basic_width); tax += 0.20 * b; remaining -= b;
43    if remaining > 0.0 { let h = remaining.min(higher_width); tax += 0.40 * h; remaining -= h; }
44    if remaining > 0.0 { tax += 0.45 * remaining; }
45    tax
46}
47
48/// Class 1 employee National Insurance for annual earnings.
49pub fn national_insurance(earnings: f64) -> f64 {
50    if earnings <= NI_PRIMARY_THRESHOLD { return 0.0; }
51    let main_band = (earnings.min(NI_UPPER_LIMIT) - NI_PRIMARY_THRESHOLD).max(0.0);
52    let upper = (earnings - NI_UPPER_LIMIT).max(0.0);
53    NI_MAIN_RATE * main_band + NI_UPPER_RATE * upper
54}
55
56/// Take-home = gross - income tax - NI. Returns (net, tax, ni).
57pub fn take_home(gross: f64) -> (f64, f64, f64) {
58    let t = income_tax(gross);
59    let n = national_insurance(gross);
60    (gross - t - n, t, n)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    #[test]
67    fn basic_rate_only() {
68        // £40,000: allowance 12,570 -> taxable 27,430 @ 20%
69        let t = income_tax(40_000.0);
70        assert!((t - 0.20 * 27_430.0).abs() < 1e-6);
71    }
72    #[test]
73    fn nil_allowance_at_125140() {
74        assert!(personal_allowance(125_140.0).abs() < 1e-6);
75    }
76    #[test]
77    fn ni_main_band() {
78        // £50,000 earnings: 8% on (50,000-12,570)
79        let n = national_insurance(50_000.0);
80        assert!((n - 0.08 * (50_000.0 - 12_570.0)).abs() < 1e-6);
81    }
82    #[test]
83    fn ni_upper_rate() {
84        // £60,000: 8% on UEL-PT + 2% on (60,000-UEL)
85        let n = national_insurance(60_000.0);
86        let expect = 0.08 * (50_270.0 - 12_570.0) + 0.02 * (60_000.0 - 50_270.0);
87        assert!((n - expect).abs() < 1e-6);
88    }
89    #[test]
90    fn take_home_decreases_with_tax() {
91        let (net, _, _) = take_home(50_000.0);
92        assert!(net < 50_000.0);
93    }
94}