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