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 = ~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 }