tallystick/
quota.rs

1use super::Numeric;
2use num_traits::Num;
3
4/// A quota defines how many votes are required to win an election in relation to the total number of votes cast. `nightly`
5pub enum Quota<C> {
6    /// Droop quota. It is defined as:
7    ///
8    /// ```floor((total-votes / (total-seats + 1)) + 1```
9    ///
10    /// In single-winner elections, it's often known as "fifty percent plus one".
11    /// The Droop quota is always an integer, even when using fractional votes.
12    ///
13    /// See [wikipedia](https://en.wikipedia.org/wiki/Droop_quota) for more details.
14    Droop,
15
16    /// Hagenbach-Bischoff quota.
17    ///
18    /// Also known as the "Newland-Britton quota" or the "exact Droop quota", it is defined as:
19    ///
20    /// ```total-votes / (total-seats + 1)```
21    ///
22    /// It differs from the Droop quota in that the quota often contains a fraction. In single-winner elections,
23    /// the first candidate to achieve more than 50% of the vote wins. This system is best used when fractional
24    /// votes are being used, or in a transferable-vote system where votes are redistributed fractionally.
25    ///
26    /// See [wikipedia](https://en.wikipedia.org/wiki/Hagenbach-Bischoff_quota) for more details.
27    Hagenbach,
28
29    /// Hare quota.
30    ///
31    /// It is defined as:
32    ///
33    /// ```total-votes / total-seats```
34    ///
35    /// In single-winner elections, it is equal to one hundred percent of the vote.
36    /// It is generally not recommended and is included for completeness.
37    ///
38    /// See [wikipedia](https://en.wikipedia.org/wiki/Hare_quota) for more details.
39    Hare,
40
41    /// Imperiali quota.
42    ///
43    /// It is defined as:
44    ///
45    /// ```total-votes / (total-seats + 2)```
46    ///
47    /// It is rarely used and not recommended.
48    ///
49    /// See [wikipedia](https://en.wikipedia.org/wiki/Imperiali_quota) for more details.
50    Imperiali,
51
52    /// Static quota that does not vary with either the total votes nor the total seats.
53    ///
54    /// Useful for oddball custom tallies with custom quotas.
55    Static(C),
56}
57
58impl<C: Numeric + Num + Clone> Quota<C> {
59    /// Compute the threshold needed to be elected for the given quota.
60    ///
61    /// Note that total-votes should be the number of votes counted in the tally.
62    /// It should not include invalid votes that were not added the tally.
63    /// For weighted tallies, it should be the sum of all weights.
64    ///
65    /// # Panics
66    /// This method will panic if `Quota::Hagenbach` is used with an integer (non Real) count type.
67    pub fn threshold(&self, total_votes: C, num_winners: C) -> C {
68        match self {
69            Quota::Droop => (total_votes / (num_winners + C::one())).floor() + C::one(),
70            Quota::Hagenbach => {
71                if !C::fraction() {
72                    panic!("tallystick::Quota::Hagenbach cannot be used with an integer count type. Please use a float or a rational.")
73                }
74                total_votes / (num_winners + C::one())
75            }
76            Quota::Hare => total_votes / num_winners,
77            Quota::Imperiali => total_votes / (num_winners + C::one() + C::one()),
78            Quota::Static(x) => x.clone(),
79        }
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    #[allow(clippy::float_cmp)]
89    fn quota_test() {
90        // Static
91        // ------
92        assert!(Quota::Static(50).threshold(100, 1) == 50);
93        assert!(Quota::Static(50).threshold(101, 1) == 50);
94
95        // Integers
96        // --------
97        assert!(Quota::Droop.threshold(100, 1) == 51);
98        assert!(Quota::Droop.threshold(101, 1) == 51);
99        assert!(Quota::Droop.threshold(102, 1) == 52);
100
101        assert!(Quota::Droop.threshold(100, 2) == 34);
102        assert!(Quota::Droop.threshold(101, 2) == 34);
103        assert!(Quota::Droop.threshold(102, 2) == 35);
104
105        assert!(Quota::Hare.threshold(100, 1) == 100);
106        assert!(Quota::Hare.threshold(101, 1) == 101);
107        assert!(Quota::Hare.threshold(102, 1) == 102);
108
109        assert!(Quota::Hare.threshold(100, 2) == 50);
110        assert!(Quota::Hare.threshold(101, 2) == 50); // 50.5 rounded down because integer
111        assert!(Quota::Hare.threshold(102, 2) == 51);
112
113        assert!(Quota::Imperiali.threshold(100, 1) == 33);
114        assert!(Quota::Imperiali.threshold(101, 1) == 33);
115        assert!(Quota::Imperiali.threshold(102, 1) == 34);
116
117        assert!(Quota::Imperiali.threshold(100, 2) == 25);
118        assert!(Quota::Imperiali.threshold(101, 2) == 25); // 25.25 rounded down because integer
119        assert!(Quota::Imperiali.threshold(102, 2) == 25); // 25.50 rounded down because integer
120
121        // Floats
122        // ------
123        let thirty_three_point_threes = 33.0 + (1.0 / 3.0); // 33.333...
124        let thirty_three_point_sixes = 33.0 + (2.0 / 3.0); // 33.666...
125
126        assert!(Quota::Droop.threshold(100.0, 1.0) == 51.0);
127        assert!(Quota::Droop.threshold(101.0, 1.0) == 51.0);
128        assert!(Quota::Droop.threshold(102.0, 1.0) == 52.0);
129
130        assert!(Quota::Droop.threshold(100.0, 2.0) == 34.0);
131        assert!(Quota::Droop.threshold(101.0, 2.0) == 34.0);
132        assert!(Quota::Droop.threshold(102.0, 2.0) == 35.0);
133
134        assert!(Quota::Hagenbach.threshold(100.0, 1.0) == 50.0);
135        assert!(Quota::Hagenbach.threshold(101.0, 1.0) == 50.5);
136        assert!(Quota::Hagenbach.threshold(102.0, 1.0) == 51.0);
137
138        assert!(Quota::Hagenbach.threshold(100.0, 2.0) == thirty_three_point_threes); // 33.333...
139        assert!(Quota::Hagenbach.threshold(101.0, 2.0) == thirty_three_point_sixes); // 33.666...
140        assert!(Quota::Hagenbach.threshold(102.0, 2.0) == 34.0);
141
142        assert!(Quota::Hare.threshold(100.0, 1.0) == 100.0);
143        assert!(Quota::Hare.threshold(101.0, 1.0) == 101.0);
144        assert!(Quota::Hare.threshold(102.0, 1.0) == 102.0);
145
146        assert!(Quota::Hare.threshold(100.0, 2.0) == 50.0);
147        assert!(Quota::Hare.threshold(101.0, 2.0) == 50.5);
148        assert!(Quota::Hare.threshold(102.0, 2.0) == 51.0);
149
150        assert!(Quota::Imperiali.threshold(100.0, 1.0) == thirty_three_point_threes); // 33.333...
151        assert!(Quota::Imperiali.threshold(101.0, 1.0) == thirty_three_point_sixes); // 33.666...
152        assert!(Quota::Imperiali.threshold(102.0, 1.0) == 34.0);
153
154        assert!(Quota::Imperiali.threshold(100.0, 2.0) == 25.00);
155        assert!(Quota::Imperiali.threshold(101.0, 2.0) == 25.25);
156        assert!(Quota::Imperiali.threshold(102.0, 2.0) == 25.50);
157    }
158
159    #[test]
160    #[should_panic]
161    fn quota_panic_test() {
162        // Hagenbach should panic when using integers
163        assert!(Quota::Hagenbach.threshold(100, 1) == 50);
164    }
165}