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