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 }