1 module bigfixed;
2 
3 import std.bigint : BigInt;
4 import std.traits : isIntegral;
5 
6 /**
7     A struct representing Fixed point number.
8 */
9 struct BigFixed
10 {
11 private:
12     BigInt data;
13     size_t Q;
14 public:
15     /// Construct BigFixed from a built-in integral type
16     this(T)(T x, size_t Q) pure nothrow if (isIntegral!T)
17     {
18         data = BigInt(x);
19         this.Q = Q;
20         data <<= this.Q;
21     }
22     /// Construct BigFixed from BigInt
23     this(T : BigInt)(T x, size_t Q) pure nothrow
24     {
25         data = BigInt(x);
26         this.Q = Q;
27         data <<= this.Q;
28     }
29     ///
30     @system unittest
31     {
32         immutable ulong i = ulong.max;
33         immutable bfi1 = BigFixed(i, 10);
34         immutable bfi2 = BigFixed(BigInt(ulong.max), 10);
35         assert(bfi1 == bfi2);
36     }
37 
38     ///
39     BigFixed convertQ(size_t newQ) pure nothrow
40     {
41         if (this.Q != newQ)
42         {
43             sizediff_t diff = (cast(long) newQ) - this.Q;
44             data <<= diff;
45             this.Q = newQ;
46         }
47         return this;
48     }
49     /// the number of fractional bits
50     @property size_t fractional_bits() pure nothrow
51     {
52         return this.Q;
53     }
54     ///
55     @system unittest
56     {
57         auto b = BigFixed(5, 10);
58 
59         b.convertQ(5);
60         assert(b == BigFixed(5, 5));
61 
62         assert(b.convertQ(10) == BigFixed(5, 10));
63         assert(b.fractional_bits == 10);
64     }
65 
66     /// Assignment from built-in integer types
67     BigFixed opAssign(T)(T x) pure nothrow if (isIntegral!T)
68     {
69         data = BigInt(x);
70         data <<= this.Q;
71         return this;
72     }
73     ///
74     @system unittest
75     {
76         auto b = BigFixed(5, 10);
77         b = 2;
78         assert(b == BigFixed(2, 10));
79     }
80 
81     /// Assignment from another BigFixed
82     BigFixed opAssign(T : BigFixed)(T x) pure @nogc
83     {
84         data = x.data;
85         this.Q = x.Q;
86         return this;
87     }
88     ///
89     @system unittest
90     {
91         immutable b1 = BigFixed(123, 5);
92         auto b2 = BigFixed(456, 10);
93         b2 = b1;
94         assert(b2 == BigFixed(123, 5));
95     }
96 
97     /// Convert the BigFixed to string
98     string toDecimalString(size_t decimal_digits)
99     {
100         import std.conv : to;
101         import std.string : rightJustify;
102 
103         auto b = this.data * (10 ^^ decimal_digits);
104         b >>= this.Q;
105         immutable str = b.to!string;
106         immutable sign = (this.data < 0) ? "-" : "";
107         immutable begin = sign.length;
108         if (str.length - begin <= decimal_digits)
109         {
110             return sign ~ "0." ~ rightJustify(str[begin .. $], decimal_digits, '0');
111         }
112         else
113         {
114             return sign ~ str[begin .. $ - decimal_digits] ~ "." ~ str[$ - decimal_digits .. $];
115         }
116     }
117 
118     /// Minimum number that can be represented greater than 0
119     @property BigFixed resolution()
120     {
121         auto result = BigFixed(0, this.Q);
122         result.data = 1;
123         return result;
124     }
125     ///
126     @system unittest
127     {
128         auto b1 = BigFixed(0, 1).resolution;
129         assert(b1.toDecimalString(1) == "0.5");
130 
131         auto b2 = BigFixed(100, 1).resolution;
132         assert(b2.toDecimalString(1) == "0.5");
133 
134         auto b3 = BigFixed(100, 3).resolution;
135         assert(b3.toDecimalString(3) == "0.125");
136     }
137     /// Implements assignment operators from BigFixed of the form `BigFixed op= BigFixed`
138     BigFixed opOpAssign(string op, T : BigFixed)(T y) pure nothrow 
139             if (op == "+" || op == "-" || op == "*" || op == "/")
140     {
141         static if (op == "+" || op == "-")
142         {
143             (this.data).opOpAssign!op(y.convertQ(this.Q).data);
144         }
145         else static if (op == "*")
146         {
147             this.data *= y.convertQ(this.Q).data;
148             this.data >>= this.Q;
149         }
150         else static if (op == "/")
151         {
152             this.data <<= this.Q;
153             this.data /= y.convertQ(this.Q).data;
154         }
155         else
156             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
157         return this;
158     }
159 
160     @system unittest
161     {
162         auto b1 = BigFixed(1, 10);
163         b1 /= BigFixed(4, 10);
164         assert(b1.toDecimalString(2) == "0.25");
165         b1 += BigFixed(1, 0);
166         assert(b1.toDecimalString(2) == "1.25");
167         b1 *= BigFixed(2, 5);
168         assert(b1.toDecimalString(2) == "2.50");
169         b1 -= BigFixed(1, 0);
170         assert(b1.toDecimalString(2) == "1.50");
171     }
172     /// Implements assignment operators from built-in integers of the form `BigFixed op= Integer`
173     BigFixed opOpAssign(string op, T)(T y) pure nothrow 
174             if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>"
175                 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T)
176     {
177         static if (op == "+" || op == "-")
178         {
179             (this.data).opOpAssign!op(BigInt(y) << this.Q);
180         }
181         else static if (op == "*" || op == "/" || op == ">>" || op == "<<"
182                 || op == "|" || op == "&" || op == "^")
183         {
184             (this.data).opOpAssign!op(y);
185         }
186         else
187             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
188         return this;
189     }
190     ///
191     @system unittest
192     {
193         auto b1 = BigFixed(1, 10);
194         b1 /= 4;
195         assert(b1.toDecimalString(2) == "0.25");
196         b1 += 1;
197         assert(b1.toDecimalString(2) == "1.25");
198         b1 *= 2;
199         assert(b1.toDecimalString(2) == "2.50");
200         b1 -= 1;
201         assert(b1.toDecimalString(2) == "1.50");
202         b1 <<= 1;
203         assert(b1.toDecimalString(2) == "3.00");
204         b1 >>= 2;
205         assert(b1.toDecimalString(2) == "0.75");
206         b1 |= (1 << 5);
207         assert(b1.toDecimalString(5) == "0.78125");
208         b1 &= (1 << 5);
209         assert(b1.toDecimalString(5) == "0.03125");
210         b1 ^= (1 << 5);
211         assert(b1.toDecimalString(5) == "0.00000");
212     }
213     /// Implements bitwise assignment operators from BigInt of the form `BigFixed op= BigInt
214     BigFixed opOpAssign(string op, T : BigInt)(T y) pure nothrow 
215             if (op == "+" || op == "-" || op == "*" || op == "/" || op == "|" || op == "&"
216                 || op == "^")
217     {
218         static if (op == "+" || op == "-")
219         {
220             (this.data).opOpAssign!op(y << this.Q);
221         }
222         else static if (op == "*" || op == "/" || op == "|" || op == "&" || op == "^")
223         {
224             (this.data).opOpAssign!op(y);
225         }
226         else
227             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
228         return this;
229     }
230     ///
231     @system unittest
232     {
233         auto b1 = BigFixed(1, 10);
234         b1 /= BigInt(4);
235         assert(b1.toDecimalString(2) == "0.25");
236         b1 += BigInt(1);
237         assert(b1.toDecimalString(2) == "1.25");
238         b1 *= BigInt(2);
239         assert(b1.toDecimalString(2) == "2.50");
240         b1 -= BigInt(1);
241         assert(b1.toDecimalString(2) == "1.50");
242         b1 |= BigInt(1 << 5);
243         assert(b1.toDecimalString(5) == "1.53125");
244         b1 &= BigInt(1 << 5);
245         assert(b1.toDecimalString(5) == "0.03125");
246         b1 ^= BigInt(1 << 5);
247         assert(b1.toDecimalString(5) == "0.00000");
248     }
249     /// Implements binary operators between BigFixed
250     BigFixed opBinary(string op, T : BigFixed)(T y) pure nothrow const 
251             if (op == "+" || op == "*" || op == "-" || op == "/")
252     {
253         BigFixed r = this;
254         return r.opOpAssign!(op)(y);
255     }
256     ///
257     @system unittest
258     {
259         auto b1 = BigFixed(1, 10);
260         assert((b1 / BigFixed(4, 10)).toDecimalString(2) == "0.25");
261         assert((b1 + BigFixed(2, 10)).toDecimalString(2) == "3.00");
262         assert((b1 * BigFixed(2, 10)).toDecimalString(2) == "2.00");
263         assert((b1 - BigFixed(1, 10)).toDecimalString(2) == "0.00");
264     }
265     /// Implements binary operators between BigInt
266     BigFixed opBinary(string op, T : BigInt)(T y) pure nothrow const 
267             if (op == "|" || op == "&" || op == "^")
268     {
269         BigFixed r = this;
270         return r.opOpAssign!(op)(y);
271     }
272     ///
273     @system unittest
274     {
275         auto b1 = BigFixed(1, 10);
276         assert((b1 | (BigInt(1) << 9)).toDecimalString(2) == "1.50");
277         assert((b1 & (BigInt(1) << 9)).toDecimalString(2) == "0.00");
278         assert((b1 ^ (BigInt(1) << 9)).toDecimalString(2) == "1.50");
279     }
280     /// Implements binary operators between Integer
281     BigFixed opBinary(string op, T)(T y) pure nothrow const 
282             if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>"
283                 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T)
284     {
285         BigFixed r = this;
286         return r.opOpAssign!(op)(y);
287     }
288     ///
289     @system unittest
290     {
291         auto b1 = BigFixed(1, 10);
292         assert((b1 + 1).toDecimalString(2) == "2.00");
293         assert((b1 - 1).toDecimalString(2) == "0.00");
294         assert((b1 * 2).toDecimalString(2) == "2.00");
295         assert((b1 / 2).toDecimalString(2) == "0.50");
296         assert((b1 >> 1).toDecimalString(2) == "0.50");
297         assert((b1 << 1).toDecimalString(2) == "2.00");
298         assert((b1 | (1 << 9)).toDecimalString(2) == "1.50");
299         assert((b1 & 1).toDecimalString(2) == "0.00");
300         assert((b1 ^ (1 << 9)).toDecimalString(2) == "1.50");
301     }
302     /// Implements binary operators of the form `Integer op BigFixed`
303     BigFixed opBinaryRight(string op, T)(T y) pure nothrow const 
304             if ((op == "+" || op == "*" || op == "|" || op == "&" || op == "^") && isIntegral!T)
305     {
306         return this.opBinary!(op)(y);
307     }
308     /// ditto
309     BigFixed opBinaryRight(string op, T)(T y) pure nothrow 
310             if ((op == "-" || op == "/") && isIntegral!T)
311     {
312         return BigFixed(y, this.Q).opBinary!(op)(this);
313     }
314     ///
315     @system unittest
316     {
317         auto b1 = BigFixed(1, 10);
318         assert((1 + b1).toDecimalString(2) == "2.00");
319         assert((1 - b1).toDecimalString(2) == "0.00");
320         assert((2 * b1).toDecimalString(2) == "2.00");
321         assert((2 / b1).toDecimalString(2) == "2.00");
322         assert(((1 << 9) | b1).toDecimalString(2) == "1.50");
323         assert((1 & b1).toDecimalString(2) == "0.00");
324         assert(((1 << 9) ^ b1).toDecimalString(2) == "1.50");
325     }
326 }