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 == "+")
142         {
143             this.data += y.convertQ(this.Q).data;
144         }
145         else static if (op == "-")
146         {
147             this.data -= y.convertQ(this.Q).data;
148         }
149         else static if (op == "*")
150         {
151             this.data *= y.convertQ(this.Q).data;
152             this.data >>= this.Q;
153         }
154         else static if (op == "/")
155         {
156             this.data <<= this.Q;
157             this.data /= y.convertQ(this.Q).data;
158         }
159         else
160             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
161         return this;
162     }
163 
164     @system unittest
165     {
166         auto b1 = BigFixed(1, 10);
167         b1 /= BigFixed(4, 10);
168         assert(b1.toDecimalString(2) == "0.25");
169         b1 += BigFixed(1, 0);
170         assert(b1.toDecimalString(2) == "1.25");
171         b1 *= BigFixed(2, 5);
172         assert(b1.toDecimalString(2) == "2.50");
173         b1 -= BigFixed(1, 0);
174         assert(b1.toDecimalString(2) == "1.50");
175     }
176     /// Implements assignment operators from built-in integers of the form `BigFixed op= Integer`
177     BigFixed opOpAssign(string op, T)(T y) pure nothrow 
178             if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>"
179                 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T)
180     {
181         static if (op == "+")
182         {
183             this.data += (BigInt(y) << this.Q);
184         }
185         else static if (op == "-")
186         {
187             this.data -= (BigInt(y) << this.Q);
188         }
189         else static if (op == "*")
190         {
191             this.data *= y;
192         }
193         else static if (op == "/")
194         {
195             this.data /= y;
196         }
197         else static if (op == ">>")
198         {
199             this.data >>= y;
200         }
201         else static if (op == "<<")
202         {
203             this.data <<= y;
204         }
205         else static if (op == "|")
206         {
207             this.data |= y;
208         }
209         else static if (op == "&")
210         {
211             this.data &= y;
212         }
213         else static if (op == "^")
214         {
215             this.data ^= y;
216         }
217         else
218             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
219         return this;
220     }
221     ///
222     @system unittest
223     {
224         auto b1 = BigFixed(1, 10);
225         b1 /= 4;
226         assert(b1.toDecimalString(2) == "0.25");
227         b1 += 1;
228         assert(b1.toDecimalString(2) == "1.25");
229         b1 *= 2;
230         assert(b1.toDecimalString(2) == "2.50");
231         b1 -= 1;
232         assert(b1.toDecimalString(2) == "1.50");
233         b1 <<= 1;
234         assert(b1.toDecimalString(2) == "3.00");
235         b1 >>= 2;
236         assert(b1.toDecimalString(2) == "0.75");
237         b1 |= (1 << 5);
238         assert(b1.toDecimalString(5) == "0.78125");
239         b1 &= (1 << 5);
240         assert(b1.toDecimalString(5) == "0.03125");
241         b1 ^= (1 << 5);
242         assert(b1.toDecimalString(5) == "0.00000");
243     }
244     /// Implements bitwise assignment operators from BigInt of the form `BigFixed op= BigInt
245     BigFixed opOpAssign(string op, T : BigInt)(T y) pure nothrow 
246             if (op == "+" || op == "-" || op == "*" || op == "/" || op == "|" || op == "&"
247                 || op == "^")
248     {
249         static if (op == "+" || op == "-")
250         {
251             (this.data).opOpAssign!op(y << this.Q);
252         }
253         else static if (op == "*" || op == "/" || op == "|" || op == "&" || op == "^")
254         {
255             (this.data).opOpAssign!op(y);
256         }
257         else
258             static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported");
259         return this;
260     }
261     ///
262     @system unittest
263     {
264         auto b1 = BigFixed(1, 10);
265         b1 /= BigInt(4);
266         assert(b1.toDecimalString(2) == "0.25");
267         b1 += BigInt(1);
268         assert(b1.toDecimalString(2) == "1.25");
269         b1 *= BigInt(2);
270         assert(b1.toDecimalString(2) == "2.50");
271         b1 -= BigInt(1);
272         assert(b1.toDecimalString(2) == "1.50");
273         b1 |= BigInt(1 << 5);
274         assert(b1.toDecimalString(5) == "1.53125");
275         b1 &= BigInt(1 << 5);
276         assert(b1.toDecimalString(5) == "0.03125");
277         b1 ^= BigInt(1 << 5);
278         assert(b1.toDecimalString(5) == "0.00000");
279     }
280     /// Implements binary operators between BigFixed
281     BigFixed opBinary(string op, T : BigFixed)(T y) pure nothrow const 
282             if (op == "+" || op == "*" || op == "-" || op == "/")
283     {
284         BigFixed r = this;
285         return r.opOpAssign!(op)(y);
286     }
287     ///
288     @system unittest
289     {
290         auto b1 = BigFixed(1, 10);
291         assert((b1 / BigFixed(4, 10)).toDecimalString(2) == "0.25");
292         assert((b1 + BigFixed(2, 10)).toDecimalString(2) == "3.00");
293         assert((b1 * BigFixed(2, 10)).toDecimalString(2) == "2.00");
294         assert((b1 - BigFixed(1, 10)).toDecimalString(2) == "0.00");
295     }
296 }