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 }