1 module bigfixed; 2 3 import std.bigint : BigInt; 4 import std.traits : isIntegral; 5 6 /** 7 A struct representing Fixed point number. 8 */ 9 struct BigFixed 10 { 11 private: 12 BigInt data; 13 size_t Q; 14 public: 15 /// Construct BigFixed from a built-in integral type 16 this(T)(T x, size_t Q) pure nothrow if (isIntegral!T) 17 { 18 data = BigInt(x); 19 this.Q = Q; 20 data <<= this.Q; 21 } 22 /// Construct BigFixed from BigInt 23 this(T : BigInt)(T x, size_t Q) pure nothrow 24 { 25 data = BigInt(x); 26 this.Q = Q; 27 data <<= this.Q; 28 } 29 /// 30 @system unittest 31 { 32 immutable ulong i = ulong.max; 33 immutable bfi1 = BigFixed(i, 10); 34 immutable bfi2 = BigFixed(BigInt(ulong.max), 10); 35 assert(bfi1 == bfi2); 36 } 37 38 /// 39 BigFixed convertQ(size_t newQ) pure nothrow 40 { 41 if (this.Q != newQ) 42 { 43 BigFixed b = this; 44 sizediff_t diff = (cast(long) newQ) - b.Q; 45 b.data <<= diff; 46 b.Q = newQ; 47 return b; 48 } 49 else 50 { 51 return this; 52 53 } 54 } 55 /// the number of fractional bits 56 @property size_t fractional_bits() pure nothrow 57 { 58 return this.Q; 59 } 60 /// 61 @system unittest 62 { 63 auto b = BigFixed(5, 10); 64 65 assert(b.convertQ(10) == BigFixed(5, 10)); 66 assert(b.fractional_bits == 10); 67 } 68 69 /// Assignment from built-in integer types 70 BigFixed opAssign(T)(T x) pure nothrow if (isIntegral!T) 71 { 72 data = BigInt(x); 73 data <<= this.Q; 74 return this; 75 } 76 /// 77 @system unittest 78 { 79 auto b = BigFixed(5, 10); 80 b = 2; 81 assert(b == BigFixed(2, 10)); 82 } 83 84 /// Assignment from another BigFixed 85 BigFixed opAssign(T : BigFixed)(T x) pure @nogc 86 { 87 data = x.data; 88 this.Q = x.Q; 89 return this; 90 } 91 /// 92 @system unittest 93 { 94 immutable b1 = BigFixed(123, 5); 95 auto b2 = BigFixed(456, 10); 96 b2 = b1; 97 assert(b2 == BigFixed(123, 5)); 98 } 99 100 /// Convert the BigFixed to string 101 string toDecimalString(size_t decimal_digits) 102 { 103 import std.conv : to; 104 import std.string : rightJustify; 105 106 auto b = this.data * (10 ^^ decimal_digits); 107 b >>= this.Q; 108 immutable str = b.to!string; 109 immutable sign = (this.data < 0) ? "-" : ""; 110 immutable begin = sign.length; 111 if (str.length - begin <= decimal_digits) 112 { 113 return sign ~ "0." ~ rightJustify(str[begin .. $], decimal_digits, '0'); 114 } 115 else 116 { 117 return sign ~ str[begin .. $ - decimal_digits] ~ "." ~ str[$ - decimal_digits .. $]; 118 } 119 } 120 121 /// Minimum number that can be represented greater than 0 122 @property BigFixed resolution() pure 123 { 124 auto result = BigFixed(0, this.Q); 125 result.data = 1; 126 return result; 127 } 128 /// 129 @system unittest 130 { 131 auto b1 = BigFixed(0, 1).resolution; 132 assert(b1.toDecimalString(1) == "0.5"); 133 134 auto b2 = BigFixed(100, 1).resolution; 135 assert(b2.toDecimalString(1) == "0.5"); 136 137 auto b3 = BigFixed(100, 3).resolution; 138 assert(b3.toDecimalString(3) == "0.125"); 139 } 140 /// Implements assignment operators from BigFixed of the form `BigFixed op= BigFixed` 141 BigFixed opOpAssign(string op, T : BigFixed)(T y) pure nothrow 142 if (op == "+" || op == "-" || op == "*" || op == "/") 143 { 144 static if (op == "+" || op == "-") 145 { 146 (this.data).opOpAssign!op(y.convertQ(this.Q).data); 147 } 148 else static if (op == "*") 149 { 150 this.data *= y.convertQ(this.Q).data; 151 this.data >>= this.Q; 152 } 153 else static if (op == "/") 154 { 155 this.data <<= this.Q; 156 this.data /= y.convertQ(this.Q).data; 157 } 158 else 159 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 160 return this; 161 } 162 163 @system unittest 164 { 165 auto b1 = BigFixed(1, 10); 166 b1 /= BigFixed(4, 10); 167 assert(b1.toDecimalString(2) == "0.25"); 168 b1 += BigFixed(1, 0); 169 assert(b1.toDecimalString(2) == "1.25"); 170 b1 *= BigFixed(2, 5); 171 assert(b1.toDecimalString(2) == "2.50"); 172 b1 -= BigFixed(1, 0); 173 assert(b1.toDecimalString(2) == "1.50"); 174 } 175 /// Implements assignment operators from built-in integers of the form `BigFixed op= Integer` 176 BigFixed opOpAssign(string op, T)(T y) pure nothrow 177 if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>" 178 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T) 179 { 180 static if (op == "+" || op == "-") 181 { 182 (this.data).opOpAssign!op(BigInt(y) << this.Q); 183 } 184 else static if (op == "*" || op == "/" || op == ">>" || op == "<<" 185 || op == "|" || op == "&" || op == "^") 186 { 187 (this.data).opOpAssign!op(y); 188 } 189 else 190 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 191 return this; 192 } 193 /// 194 @system unittest 195 { 196 auto b1 = BigFixed(1, 10); 197 b1 /= 4; 198 assert(b1.toDecimalString(2) == "0.25"); 199 b1 += 1; 200 assert(b1.toDecimalString(2) == "1.25"); 201 b1 *= 2; 202 assert(b1.toDecimalString(2) == "2.50"); 203 b1 -= 1; 204 assert(b1.toDecimalString(2) == "1.50"); 205 b1 <<= 1; 206 assert(b1.toDecimalString(2) == "3.00"); 207 b1 >>= 2; 208 assert(b1.toDecimalString(2) == "0.75"); 209 b1 |= (1 << 5); 210 assert(b1.toDecimalString(5) == "0.78125"); 211 b1 &= (1 << 5); 212 assert(b1.toDecimalString(5) == "0.03125"); 213 b1 ^= (1 << 5); 214 assert(b1.toDecimalString(5) == "0.00000"); 215 } 216 /// Implements bitwise assignment operators from BigInt of the form `BigFixed op= BigInt` 217 BigFixed opOpAssign(string op, T : BigInt)(T y) pure nothrow 218 if (op == "+" || op == "-" || op == "*" || op == "/" || op == "|" || op == "&" 219 || op == "^") 220 { 221 static if (op == "+" || op == "-") 222 { 223 (this.data).opOpAssign!op(y << this.Q); 224 } 225 else static if (op == "*" || op == "/" || op == "|" || op == "&" || op == "^") 226 { 227 (this.data).opOpAssign!op(y); 228 } 229 else 230 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 231 return this; 232 } 233 /// 234 @system unittest 235 { 236 auto b1 = BigFixed(1, 10); 237 b1 /= BigInt(4); 238 assert(b1.toDecimalString(2) == "0.25"); 239 b1 += BigInt(1); 240 assert(b1.toDecimalString(2) == "1.25"); 241 b1 *= BigInt(2); 242 assert(b1.toDecimalString(2) == "2.50"); 243 b1 -= BigInt(1); 244 assert(b1.toDecimalString(2) == "1.50"); 245 b1 |= BigInt(1 << 5); 246 assert(b1.toDecimalString(5) == "1.53125"); 247 b1 &= BigInt(1 << 5); 248 assert(b1.toDecimalString(5) == "0.03125"); 249 b1 ^= BigInt(1 << 5); 250 assert(b1.toDecimalString(5) == "0.00000"); 251 } 252 /// Implements binary operators between BigFixed 253 BigFixed opBinary(string op, T : BigFixed)(T y) pure nothrow const 254 if (op == "+" || op == "*" || op == "-" || op == "/") 255 { 256 BigFixed r = this; 257 return r.opOpAssign!(op)(y); 258 } 259 /// 260 @system unittest 261 { 262 auto b1 = BigFixed(1, 10); 263 assert((b1 / BigFixed(4, 10)).toDecimalString(2) == "0.25"); 264 assert((b1 + BigFixed(2, 10)).toDecimalString(2) == "3.00"); 265 assert((b1 * BigFixed(2, 10)).toDecimalString(2) == "2.00"); 266 assert((b1 - BigFixed(1, 10)).toDecimalString(2) == "0.00"); 267 } 268 /// Implements binary operators between BigInt 269 BigFixed opBinary(string op, T : BigInt)(T y) pure nothrow const 270 if (op == "|" || op == "&" || op == "^") 271 { 272 BigFixed r = this; 273 return r.opOpAssign!(op)(y); 274 } 275 /// 276 @system unittest 277 { 278 auto b1 = BigFixed(1, 10); 279 assert((b1 | (BigInt(1) << 9)).toDecimalString(2) == "1.50"); 280 assert((b1 & (BigInt(1) << 9)).toDecimalString(2) == "0.00"); 281 assert((b1 ^ (BigInt(1) << 9)).toDecimalString(2) == "1.50"); 282 } 283 /// Implements binary operators between Integer 284 BigFixed opBinary(string op, T)(T y) pure nothrow const 285 if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>" 286 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T) 287 { 288 BigFixed r = this; 289 return r.opOpAssign!(op)(y); 290 } 291 /// 292 @system unittest 293 { 294 auto b1 = BigFixed(1, 10); 295 assert((b1 + 1).toDecimalString(2) == "2.00"); 296 assert((b1 - 1).toDecimalString(2) == "0.00"); 297 assert((b1 * 2).toDecimalString(2) == "2.00"); 298 assert((b1 / 2).toDecimalString(2) == "0.50"); 299 assert((b1 >> 1).toDecimalString(2) == "0.50"); 300 assert((b1 << 1).toDecimalString(2) == "2.00"); 301 assert((b1 | (1 << 9)).toDecimalString(2) == "1.50"); 302 assert((b1 & 1).toDecimalString(2) == "0.00"); 303 assert((b1 ^ (1 << 9)).toDecimalString(2) == "1.50"); 304 } 305 /// Implements binary operators of the form `Integer op BigFixed` 306 BigFixed opBinaryRight(string op, T)(T y) pure nothrow const 307 if ((op == "+" || op == "*" || op == "|" || op == "&" || op == "^") && isIntegral!T) 308 { 309 return this.opBinary!(op)(y); 310 } 311 /// ditto 312 BigFixed opBinaryRight(string op, T)(T y) pure nothrow 313 if ((op == "-" || op == "/") && isIntegral!T) 314 { 315 return BigFixed(y, this.Q).opBinary!(op)(this); 316 } 317 /// 318 @system unittest 319 { 320 auto b1 = BigFixed(1, 10); 321 assert((1 + b1).toDecimalString(2) == "2.00"); 322 assert((1 - b1).toDecimalString(2) == "0.00"); 323 assert((2 * b1).toDecimalString(2) == "2.00"); 324 assert((2 / b1).toDecimalString(2) == "2.00"); 325 assert(((1 << 9) | b1).toDecimalString(2) == "1.50"); 326 assert((1 & b1).toDecimalString(2) == "0.00"); 327 assert(((1 << 9) ^ b1).toDecimalString(2) == "1.50"); 328 } 329 /// Implements BigFixed equality test 330 bool opEquals()(auto ref const BigFixed y) const pure @nogc 331 { 332 return (this.data == y.data) && (this.Q == y.Q); 333 } 334 /// 335 @system unittest 336 { 337 auto x = BigFixed(1, 10) / 2; 338 auto y = BigFixed(5, 10) / 2; 339 assert(x == x); 340 assert(x != y); 341 assert((x + 2) == y); 342 } 343 }