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 sizediff_t diff = (cast(long) newQ) - this.Q; 44 data <<= diff; 45 this.Q = newQ; 46 } 47 return this; 48 } 49 /// the number of fractional bits 50 @property size_t fractional_bits() pure nothrow 51 { 52 return this.Q; 53 } 54 /// 55 @system unittest 56 { 57 auto b = BigFixed(5, 10); 58 59 b.convertQ(5); 60 assert(b == BigFixed(5, 5)); 61 62 assert(b.convertQ(10) == BigFixed(5, 10)); 63 assert(b.fractional_bits == 10); 64 } 65 66 /// Assignment from built-in integer types 67 BigFixed opAssign(T)(T x) pure nothrow if (isIntegral!T) 68 { 69 data = BigInt(x); 70 data <<= this.Q; 71 return this; 72 } 73 /// 74 @system unittest 75 { 76 auto b = BigFixed(5, 10); 77 b = 2; 78 assert(b == BigFixed(2, 10)); 79 } 80 81 /// Assignment from another BigFixed 82 BigFixed opAssign(T : BigFixed)(T x) pure @nogc 83 { 84 data = x.data; 85 this.Q = x.Q; 86 return this; 87 } 88 /// 89 @system unittest 90 { 91 immutable b1 = BigFixed(123, 5); 92 auto b2 = BigFixed(456, 10); 93 b2 = b1; 94 assert(b2 == BigFixed(123, 5)); 95 } 96 97 /// Convert the BigFixed to string 98 string toDecimalString(size_t decimal_digits) 99 { 100 import std.conv : to; 101 import std.string : rightJustify; 102 103 auto b = this.data * (10 ^^ decimal_digits); 104 b >>= this.Q; 105 immutable str = b.to!string; 106 immutable sign = (this.data < 0) ? "-" : ""; 107 immutable begin = sign.length; 108 if (str.length - begin <= decimal_digits) 109 { 110 return sign ~ "0." ~ rightJustify(str[begin .. $], decimal_digits, '0'); 111 } 112 else 113 { 114 return sign ~ str[begin .. $ - decimal_digits] ~ "." ~ str[$ - decimal_digits .. $]; 115 } 116 } 117 118 /// Minimum number that can be represented greater than 0 119 @property BigFixed resolution() 120 { 121 auto result = BigFixed(0, this.Q); 122 result.data = 1; 123 return result; 124 } 125 /// 126 @system unittest 127 { 128 auto b1 = BigFixed(0, 1).resolution; 129 assert(b1.toDecimalString(1) == "0.5"); 130 131 auto b2 = BigFixed(100, 1).resolution; 132 assert(b2.toDecimalString(1) == "0.5"); 133 134 auto b3 = BigFixed(100, 3).resolution; 135 assert(b3.toDecimalString(3) == "0.125"); 136 } 137 /// Implements assignment operators from BigFixed of the form `BigFixed op= BigFixed` 138 BigFixed opOpAssign(string op, T : BigFixed)(T y) pure nothrow 139 if (op == "+" || op == "-" || op == "*" || op == "/") 140 { 141 static if (op == "+" || op == "-") 142 { 143 (this.data).opOpAssign!op(y.convertQ(this.Q).data); 144 } 145 else static if (op == "*") 146 { 147 this.data *= y.convertQ(this.Q).data; 148 this.data >>= this.Q; 149 } 150 else static if (op == "/") 151 { 152 this.data <<= this.Q; 153 this.data /= y.convertQ(this.Q).data; 154 } 155 else 156 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 157 return this; 158 } 159 160 @system unittest 161 { 162 auto b1 = BigFixed(1, 10); 163 b1 /= BigFixed(4, 10); 164 assert(b1.toDecimalString(2) == "0.25"); 165 b1 += BigFixed(1, 0); 166 assert(b1.toDecimalString(2) == "1.25"); 167 b1 *= BigFixed(2, 5); 168 assert(b1.toDecimalString(2) == "2.50"); 169 b1 -= BigFixed(1, 0); 170 assert(b1.toDecimalString(2) == "1.50"); 171 } 172 /// Implements assignment operators from built-in integers of the form `BigFixed op= Integer` 173 BigFixed opOpAssign(string op, T)(T y) pure nothrow 174 if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>" 175 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T) 176 { 177 static if (op == "+" || op == "-") 178 { 179 (this.data).opOpAssign!op(BigInt(y) << this.Q); 180 } 181 else static if (op == "*" || op == "/" || op == ">>" || op == "<<" 182 || op == "|" || op == "&" || op == "^") 183 { 184 (this.data).opOpAssign!op(y); 185 } 186 else 187 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 188 return this; 189 } 190 /// 191 @system unittest 192 { 193 auto b1 = BigFixed(1, 10); 194 b1 /= 4; 195 assert(b1.toDecimalString(2) == "0.25"); 196 b1 += 1; 197 assert(b1.toDecimalString(2) == "1.25"); 198 b1 *= 2; 199 assert(b1.toDecimalString(2) == "2.50"); 200 b1 -= 1; 201 assert(b1.toDecimalString(2) == "1.50"); 202 b1 <<= 1; 203 assert(b1.toDecimalString(2) == "3.00"); 204 b1 >>= 2; 205 assert(b1.toDecimalString(2) == "0.75"); 206 b1 |= (1 << 5); 207 assert(b1.toDecimalString(5) == "0.78125"); 208 b1 &= (1 << 5); 209 assert(b1.toDecimalString(5) == "0.03125"); 210 b1 ^= (1 << 5); 211 assert(b1.toDecimalString(5) == "0.00000"); 212 } 213 /// Implements bitwise assignment operators from BigInt of the form `BigFixed op= BigInt 214 BigFixed opOpAssign(string op, T : BigInt)(T y) pure nothrow 215 if (op == "+" || op == "-" || op == "*" || op == "/" || op == "|" || op == "&" 216 || op == "^") 217 { 218 static if (op == "+" || op == "-") 219 { 220 (this.data).opOpAssign!op(y << this.Q); 221 } 222 else static if (op == "*" || op == "/" || op == "|" || op == "&" || op == "^") 223 { 224 (this.data).opOpAssign!op(y); 225 } 226 else 227 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 228 return this; 229 } 230 /// 231 @system unittest 232 { 233 auto b1 = BigFixed(1, 10); 234 b1 /= BigInt(4); 235 assert(b1.toDecimalString(2) == "0.25"); 236 b1 += BigInt(1); 237 assert(b1.toDecimalString(2) == "1.25"); 238 b1 *= BigInt(2); 239 assert(b1.toDecimalString(2) == "2.50"); 240 b1 -= BigInt(1); 241 assert(b1.toDecimalString(2) == "1.50"); 242 b1 |= BigInt(1 << 5); 243 assert(b1.toDecimalString(5) == "1.53125"); 244 b1 &= BigInt(1 << 5); 245 assert(b1.toDecimalString(5) == "0.03125"); 246 b1 ^= BigInt(1 << 5); 247 assert(b1.toDecimalString(5) == "0.00000"); 248 } 249 /// Implements binary operators between BigFixed 250 BigFixed opBinary(string op, T : BigFixed)(T y) pure nothrow const 251 if (op == "+" || op == "*" || op == "-" || op == "/") 252 { 253 BigFixed r = this; 254 return r.opOpAssign!(op)(y); 255 } 256 /// 257 @system unittest 258 { 259 auto b1 = BigFixed(1, 10); 260 assert((b1 / BigFixed(4, 10)).toDecimalString(2) == "0.25"); 261 assert((b1 + BigFixed(2, 10)).toDecimalString(2) == "3.00"); 262 assert((b1 * BigFixed(2, 10)).toDecimalString(2) == "2.00"); 263 assert((b1 - BigFixed(1, 10)).toDecimalString(2) == "0.00"); 264 } 265 /// Implements binary operators between BigInt 266 BigFixed opBinary(string op, T : BigInt)(T y) pure nothrow const 267 if (op == "|" || op == "&" || op == "^") 268 { 269 BigFixed r = this; 270 return r.opOpAssign!(op)(y); 271 } 272 /// 273 @system unittest 274 { 275 auto b1 = BigFixed(1, 10); 276 assert((b1 | (BigInt(1) << 9)).toDecimalString(2) == "1.50"); 277 assert((b1 & (BigInt(1) << 9)).toDecimalString(2) == "0.00"); 278 assert((b1 ^ (BigInt(1) << 9)).toDecimalString(2) == "1.50"); 279 } 280 /// Implements binary operators between Integer 281 BigFixed opBinary(string op, T)(T y) pure nothrow const 282 if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>" 283 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T) 284 { 285 BigFixed r = this; 286 return r.opOpAssign!(op)(y); 287 } 288 /// 289 @system unittest 290 { 291 auto b1 = BigFixed(1, 10); 292 assert((b1 + 1).toDecimalString(2) == "2.00"); 293 assert((b1 - 1).toDecimalString(2) == "0.00"); 294 assert((b1 * 2).toDecimalString(2) == "2.00"); 295 assert((b1 / 2).toDecimalString(2) == "0.50"); 296 assert((b1 >> 1).toDecimalString(2) == "0.50"); 297 assert((b1 << 1).toDecimalString(2) == "2.00"); 298 assert((b1 | (1 << 9)).toDecimalString(2) == "1.50"); 299 assert((b1 & 1).toDecimalString(2) == "0.00"); 300 assert((b1 ^ (1 << 9)).toDecimalString(2) == "1.50"); 301 } 302 }