Line data Source code
1 : /********************************************************************
2 : * gnc-rational-rounding.hpp - Template functions for rounding *
3 : * Copyright 2017 John Ralls <jralls@ceridwen.us> *
4 : * This program is free software; you can redistribute it and/or *
5 : * modify it under the terms of the GNU General Public License as *
6 : * published by the Free Software Foundation; either version 2 of *
7 : * the License, or (at your option) any later version. *
8 : * *
9 : * This program is distributed in the hope that it will be useful, *
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 : * GNU General Public License for more details. *
13 : * *
14 : * You should have received a copy of the GNU General Public License*
15 : * along with this program; if not, contact: *
16 : * *
17 : * Free Software Foundation Voice: +1-617-542-5942 *
18 : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19 : * Boston, MA 02110-1301, USA gnu@gnu.org *
20 : * *
21 : *******************************************************************/
22 :
23 : #ifndef __GNC_RATIONAL_ROUNDING_HPP__
24 : #define __GNC_RATIONAL_ROUNDING_HPP__
25 : #include "gnc-numeric.h"
26 : #include "gnc-int128.hpp"
27 :
28 : template <typename T> inline bool
29 10 : quotient_is_positive(T dividend, T divisor)
30 : {
31 10 : return (dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0);
32 : }
33 :
34 : enum class RoundType
35 : {
36 : floor = GNC_HOW_RND_FLOOR,
37 : ceiling = GNC_HOW_RND_CEIL,
38 : truncate = GNC_HOW_RND_TRUNC,
39 : promote = GNC_HOW_RND_PROMOTE,
40 : half_down = GNC_HOW_RND_ROUND_HALF_DOWN,
41 : half_up = GNC_HOW_RND_ROUND_HALF_UP,
42 : bankers = GNC_HOW_RND_ROUND,
43 : never = GNC_HOW_RND_NEVER,
44 : };
45 :
46 : enum class DenomType
47 : {
48 : den_auto = GNC_DENOM_AUTO,
49 : exact = GNC_HOW_DENOM_EXACT,
50 : reduce = GNC_HOW_DENOM_REDUCE,
51 : lcd = GNC_HOW_DENOM_LCD,
52 : fixed = GNC_HOW_DENOM_FIXED,
53 : sigfigs = GNC_HOW_DENOM_SIGFIG,
54 : };
55 :
56 :
57 : template <RoundType rt>
58 : struct RT2T
59 : {
60 : RoundType value = rt;
61 : };
62 :
63 : /* The following templates implement the rounding policies for the convert and
64 : * convert_sigfigs template functions.
65 : */
66 : template <typename T> inline T
67 84 : round(T num, T den, T rem, RT2T<RoundType::never>)
68 : {
69 84 : if (rem == 0)
70 0 : return num;
71 84 : throw std::domain_error("Rounding required when 'never round' specified.");
72 : }
73 :
74 : template <typename T> inline T
75 11 : round(T num, T den, T rem, RT2T<RoundType::floor>)
76 : {
77 : // std::cout << "Rounding to floor with num " << num << " den " << den
78 : // << ", and rem " << rem << ".\n";
79 11 : if (rem == 0)
80 0 : return num;
81 : // floor num==0 that is the quotient of two numbers with opposite signs
82 11 : if (num < 0 || (num == 0 && !quotient_is_positive(rem, den)))
83 6 : return num - 1;
84 5 : return num;
85 : }
86 :
87 :
88 : template <> inline GncInt128
89 0 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
90 : RT2T<RoundType::floor>)
91 : {
92 : // std::cout << "Rounding to floor with num " << num << " den " << den
93 : // << ", and rem " << rem << ".\n";
94 0 : if (rem == 0)
95 0 : return num;
96 0 : if (num.isNeg())
97 0 : return num - 1;
98 0 : return num;
99 : }
100 :
101 : template <typename T> inline T
102 1 : round(T num, T den, T rem, RT2T<RoundType::ceiling>)
103 : {
104 1 : if (rem == 0)
105 0 : return num;
106 1 : if (num > 0 || (num == 0 && quotient_is_positive(rem, den)))
107 1 : return num + 1;
108 0 : return num;
109 : }
110 :
111 : template <> inline GncInt128
112 0 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
113 : RT2T<RoundType::ceiling>)
114 : {
115 0 : if (rem == 0)
116 0 : return num;
117 0 : if (!num.isNeg())
118 0 : return num + 1;
119 0 : return num;
120 : }
121 :
122 : template <typename T> inline T
123 4388 : round(T num, T den, T rem, RT2T<RoundType::truncate>)
124 : {
125 4388 : return num;
126 : }
127 :
128 : template <typename T> inline T
129 0 : round(T num, T den, T rem, RT2T<RoundType::promote>)
130 : {
131 0 : if (rem == 0)
132 0 : return num;
133 0 : if (num == 0)
134 0 : return (!quotient_is_positive(rem, den) ? -1 : 1);
135 0 : return num + (num < 0 ? -1 : 1);
136 : }
137 :
138 : template <> inline GncInt128
139 0 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
140 : RT2T<RoundType::promote>)
141 : {
142 0 : if (rem == 0)
143 0 : return num;
144 0 : return num + (num.isNeg() ? -1 : 1);
145 : }
146 :
147 : template <typename T> inline T
148 0 : round(T num, T den, T rem, RT2T<RoundType::half_down>)
149 : {
150 0 : if (rem == 0)
151 0 : return num;
152 0 : if (std::abs(rem * 2) > std::abs(den))
153 : {
154 0 : if (num == 0)
155 0 : return (!quotient_is_positive(rem, den) ? -1 : 1);
156 0 : return num + (num < 0 ? -1 : 1);
157 : }
158 0 : return num;
159 : }
160 :
161 : template <> inline GncInt128
162 57 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
163 : RT2T<RoundType::half_down>)
164 : {
165 57 : if (rem == 0)
166 0 : return num;
167 57 : if (rem.abs() * 2 > den.abs())
168 23 : return num + (num.isNeg() ? -1 : 1);
169 34 : return num;
170 : }
171 :
172 : template <typename T> inline T
173 1424 : round(T num, T den, T rem, RT2T<RoundType::half_up>)
174 : {
175 1424 : if (rem == 0)
176 0 : return num;
177 1424 : if (std::abs(rem) * 2 >= std::abs(den))
178 : {
179 725 : if (num == 0)
180 9 : return (!quotient_is_positive(rem, den) ? -1 : 1);
181 716 : return num + (num < 0 ? -1 : 1);
182 : }
183 699 : return num;
184 : }
185 :
186 : template <> inline GncInt128
187 63 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
188 : RT2T<RoundType::half_up>)
189 : {
190 63 : if (rem == 0)
191 0 : return num;
192 63 : if (rem.abs() * 2 >= den.abs())
193 27 : return num + (num.isNeg() ? -1 : 1);
194 36 : return num;
195 : }
196 :
197 : template <typename T> inline T
198 301 : round(T num, T den, T rem, RT2T<RoundType::bankers>)
199 : {
200 301 : if (rem == 0)
201 0 : return num;
202 436 : if (std::abs(rem * 2) > std::abs(den) ||
203 135 : (std::abs(rem * 2) == std::abs(den) && num % 2))
204 : {
205 194 : if (num == 0)
206 0 : return (!quotient_is_positive(rem, den) ? -1 : 1);
207 194 : return num + (num < 0 ? -1 : 1);
208 : }
209 107 : return num;
210 : }
211 :
212 : template <> inline GncInt128
213 462 : round<GncInt128>(GncInt128 num, GncInt128 den, GncInt128 rem,
214 : RT2T<RoundType::bankers>)
215 : {
216 462 : if (rem == 0)
217 0 : return num;
218 519 : if (rem.abs() * 2 > den.abs() ||
219 519 : (rem.abs() * 2 == den.abs() && num % 2))
220 405 : return num + (num.isNeg() ? -1 : 1);
221 57 : return num;
222 : }
223 : #endif //__GNC_RATIONAL_ROUNDING_HPP__
|