1 module gfm.integers.fixedpoint;
2 
3 import std.traits;
4 
5 import gfm.integers.wideint;
6 
7 /**
8     M.N fixed point integer.
9     Only signed integers are supported.
10     Only supports basic arithmetic.
11     If M + N > 32, then wide integers are used and this will likely be slow.
12 
13     Params:
14         M = number of bits before the mark, M > 0
15         N = number of bits after the mark, N > 0
16 
17     Bugs: No proper rounding.
18  */
19 struct FixedPoint(int M, int N)
20 {
21     static assert(M > 0);       // M == 0 is unsupported
22     static assert(N > 0);       // N == 0 also unsupported, but in this case you can use wideint!M
23 
24     public
25     {
26         alias TypeNeeded!(N + M) value_t;
27 
28         /// Construct with an assignable value.
29         @nogc this(U)(U x) pure nothrow
30         {
31             opAssign!U(x);
32         }
33 
34         /// Construct with an assignable value.
35         @nogc ref FixedPoint opAssign(U)(U x) pure nothrow if (is(U: FixedPoint))
36         {
37             value = x.value;
38             return this;
39         }
40 
41         @nogc ref FixedPoint opAssign(U)(U x) pure nothrow if (isIntegral!U)
42         {
43             value = x * ONE;                // exact
44             return this;
45         }
46 
47         @nogc ref FixedPoint opAssign(U)(U x) pure nothrow if (isFloatingPoint!U)
48         {
49             value = cast(value_t)(x * ONE); // truncation
50             return this;
51         }
52 
53         // casting to float
54         @nogc U opCast(U)() pure const nothrow if (isFloatingPoint!U)
55         {
56             return cast(U)(value) / ONE;
57         }
58 
59         // casting to integer (truncation)
60         @nogc U opCast(U)() pure const nothrow if (isIntegral!U)
61         {
62             return cast(U)(value) >> N;
63         }
64 
65         @nogc ref FixedPoint opOpAssign(string op, U)(U x) pure nothrow if (is(U: FixedPoint))
66         {
67             static if (op == "+")
68             {
69                 value += x.value;
70                 return this;
71             }
72             else static if (op == "-")
73             {
74                 value -= x.value;
75                 return this;
76             }
77             else static if (op == "*")
78             {
79                 alias TypeNeeded!(2 * N + M) inter_t;
80                 value = cast(value_t)((cast(inter_t)value * x.value) >> N);
81                 return this;
82             }
83             else static if (op == "/")
84             {
85                 alias TypeNeeded!(2 * N + M) inter_t;
86                 value = cast(value_t)((cast(inter_t)value << N) / x.value);
87                 //             ^ possible overflow when downcasting
88                 return this;
89             }
90             else
91             {
92                 static assert(false, "FixedPoint does not support operator " ~ op);
93             }
94         }
95 
96         @nogc ref FixedPoint opOpAssign(string op, U)(U x) pure nothrow if (isConvertible!U)
97         {
98             FixedPoint conv = x;
99             return opOpAssign!op(conv);
100         }
101 
102         @nogc FixedPoint opBinary(string op, U)(U x) pure const nothrow if (is(U: FixedPoint) || (isConvertible!U))
103         {
104             FixedPoint temp = this;
105             return temp.opOpAssign!op(x);
106         }
107 
108         @nogc FixedPoint opBinaryRight(string op, U)(U x) pure const nothrow if (isConvertible!U)
109         {
110             FixedPoint temp = x;
111             return temp.opOpAssign!op(this);
112         }
113 
114         @nogc bool opEquals(U)(U other) pure const nothrow if (is(U : FixedPoint))
115         {
116             return value == other.value;
117         }
118 
119         @nogc bool opEquals(U)(U other) pure const nothrow if (isConvertible!U)
120         {
121             FixedPoint conv = other;
122             return opEquals(conv);
123         }
124 
125         @nogc int opCmp(in FixedPoint other) pure const nothrow
126         {
127             if (value > other.value)
128                 return 1;
129             else if (value < other.value)
130                 return -1;
131             else
132                 return 0;
133         }
134 
135         @nogc FixedPoint opUnary(string op)() pure const nothrow if (op == "+")
136         {
137             return this;
138         }
139 
140         @nogc FixedPoint opUnary(string op)() pure const nothrow if (op == "-")
141         {
142             FixedPoint res = void;
143             res.value = -value;
144             return res;
145         }
146 
147         value_t value;
148     }
149 
150     private
151     {
152         enum value_t ONE = cast(value_t)1 << N;
153         enum value_t HALF = ONE >>> 1;
154         enum value_t LOW_MASK = ONE - 1;
155         enum value_t HIGH_MASK = ~cast(int)(LOW_MASK);
156         static assert((ONE & LOW_MASK) == 0);
157 
158         // define types that can be converted to FixedPoint, but are not FixedPoint
159         template isConvertible(T)
160         {
161             enum bool isConvertible = (!is(T : FixedPoint))
162             && is(typeof(
163                 {
164                     T x;
165                     FixedPoint v = x;
166                 }()));
167         }
168     }
169 }
170 
171 // Selects a signed integer type suitable to hold numBits bits.
172 private template TypeNeeded(int numBits)
173 {
174     static if (numBits <= 8)
175     {
176         alias byte TypeNeeded;
177     }
178     else
179     {
180         enum N = nextPowerOf2(numBits);
181         alias wideint!N TypeNeeded;
182     }
183 }
184 
185 /// abs() function for fixed-point numbers.
186 @nogc FixedPoint!(M, N) abs(int M, int N)(FixedPoint!(M, N) x) pure nothrow
187 {
188     FixedPoint!(M, N) res = void;
189     res.value = ((x.value >= 0) ? x.value : -x.value);
190     return res;
191 }
192 
193 unittest
194 {
195     alias FixedPoint!(4,4) fix4;
196     alias FixedPoint!(8,8) fix8;
197     alias FixedPoint!(16,16) fix16;
198     alias FixedPoint!(24,8) fix24_8;
199     alias FixedPoint!(32,32) fix32_32;
200 
201     static assert (is(fix24_8.value_t == int));
202     static assert (is(fix16.value_t == int));
203     static assert (is(fix8.value_t == short));
204     static assert (is(fix4.value_t == byte));
205 
206     static assert(fix8.ONE == 0x0100);
207     static assert(fix16.ONE == 0x00010000);
208     static assert(fix24_8.ONE == 0x0100);
209 
210     fix16 a = 1, b = 2, c = 3;
211     assert(a < b);
212     assert(c >= b);
213     fix16 d;
214     auto apb = a + b;
215     auto bmc = b * c;
216     d = a + b * c;
217     assert(d.value == 7 * d.ONE);
218     assert(d == 7);
219     assert(32768 * (d / 32768) == 7);
220 }
221 
222 private bool isPowerOf2(T)(T i) pure nothrow @nogc if (isIntegral!T) 
223 {
224     assert(i >= 0);
225     return (i != 0) && ((i & (i - 1)) == 0);
226 }
227 
228 private int nextPowerOf2(int i) pure nothrow @nogc
229 {
230     int v = i - 1;
231     v |= v >> 1;
232     v |= v >> 2;
233     v |= v >> 4;
234     v |= v >> 8;
235     v |= v >> 16;
236     v++;
237     assert(isPowerOf2(v));
238     return v;
239 }