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