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