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 == "+") 142 { 143 this.data += y.convertQ(this.Q).data; 144 } 145 else static if (op == "-") 146 { 147 this.data -= y.convertQ(this.Q).data; 148 } 149 else static if (op == "*") 150 { 151 this.data *= y.convertQ(this.Q).data; 152 this.data >>= this.Q; 153 } 154 else static if (op == "/") 155 { 156 this.data <<= this.Q; 157 this.data /= y.convertQ(this.Q).data; 158 } 159 else 160 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 161 return this; 162 } 163 164 @system unittest 165 { 166 auto b1 = BigFixed(1, 10); 167 b1 /= BigFixed(4, 10); 168 assert(b1.toDecimalString(2) == "0.25"); 169 b1 += BigFixed(1, 0); 170 assert(b1.toDecimalString(2) == "1.25"); 171 b1 *= BigFixed(2, 5); 172 assert(b1.toDecimalString(2) == "2.50"); 173 b1 -= BigFixed(1, 0); 174 assert(b1.toDecimalString(2) == "1.50"); 175 } 176 /// Implements assignment operators from built-in integers of the form `BigFixed op= Integer` 177 BigFixed opOpAssign(string op, T)(T y) pure nothrow 178 if ((op == "+" || op == "-" || op == "*" || op == "/" || op == ">>" 179 || op == "<<" || op == "|" || op == "&" || op == "^") && isIntegral!T) 180 { 181 static if (op == "+") 182 { 183 this.data += (BigInt(y) << this.Q); 184 } 185 else static if (op == "-") 186 { 187 this.data -= (BigInt(y) << this.Q); 188 } 189 else static if (op == "*") 190 { 191 this.data *= y; 192 } 193 else static if (op == "/") 194 { 195 this.data /= y; 196 } 197 else static if (op == ">>") 198 { 199 this.data >>= y; 200 } 201 else static if (op == "<<") 202 { 203 this.data <<= y; 204 } 205 else static if (op == "|") 206 { 207 this.data |= y; 208 } 209 else static if (op == "&") 210 { 211 this.data &= y; 212 } 213 else static if (op == "^") 214 { 215 this.data ^= y; 216 } 217 else 218 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 219 return this; 220 } 221 /// 222 @system unittest 223 { 224 auto b1 = BigFixed(1, 10); 225 b1 /= 4; 226 assert(b1.toDecimalString(2) == "0.25"); 227 b1 += 1; 228 assert(b1.toDecimalString(2) == "1.25"); 229 b1 *= 2; 230 assert(b1.toDecimalString(2) == "2.50"); 231 b1 -= 1; 232 assert(b1.toDecimalString(2) == "1.50"); 233 b1 <<= 1; 234 assert(b1.toDecimalString(2) == "3.00"); 235 b1 >>= 2; 236 assert(b1.toDecimalString(2) == "0.75"); 237 b1 |= (1 << 5); 238 assert(b1.toDecimalString(5) == "0.78125"); 239 b1 &= (1 << 5); 240 assert(b1.toDecimalString(5) == "0.03125"); 241 b1 ^= (1 << 5); 242 assert(b1.toDecimalString(5) == "0.00000"); 243 } 244 /// Implements bitwise assignment operators from BigInt of the form `BigFixed op= BigInt 245 BigFixed opOpAssign(string op, T : BigInt)(T y) pure nothrow 246 if (op == "+" || op == "-" || op == "*" || op == "/" || op == "|" || op == "&" 247 || op == "^") 248 { 249 static if (op == "+" || op == "-") 250 { 251 (this.data).opOpAssign!op(y << this.Q); 252 } 253 else static if (op == "*" || op == "/" || op == "|" || op == "&" || op == "^") 254 { 255 (this.data).opOpAssign!op(y); 256 } 257 else 258 static assert(0, "BigFixed " ~ op[0 .. $ - 1] ~ "= " ~ T.stringof ~ " is not supported"); 259 return this; 260 } 261 /// 262 @system unittest 263 { 264 auto b1 = BigFixed(1, 10); 265 b1 /= BigInt(4); 266 assert(b1.toDecimalString(2) == "0.25"); 267 b1 += BigInt(1); 268 assert(b1.toDecimalString(2) == "1.25"); 269 b1 *= BigInt(2); 270 assert(b1.toDecimalString(2) == "2.50"); 271 b1 -= BigInt(1); 272 assert(b1.toDecimalString(2) == "1.50"); 273 b1 |= BigInt(1 << 5); 274 assert(b1.toDecimalString(5) == "1.53125"); 275 b1 &= BigInt(1 << 5); 276 assert(b1.toDecimalString(5) == "0.03125"); 277 b1 ^= BigInt(1 << 5); 278 assert(b1.toDecimalString(5) == "0.00000"); 279 } 280 /// Implements binary operators between BigFixed 281 BigFixed opBinary(string op, T : BigFixed)(T y) pure nothrow const 282 if (op == "+" || op == "*" || op == "-" || op == "/") 283 { 284 BigFixed r = this; 285 return r.opOpAssign!(op)(y); 286 } 287 /// 288 @system unittest 289 { 290 auto b1 = BigFixed(1, 10); 291 assert((b1 / BigFixed(4, 10)).toDecimalString(2) == "0.25"); 292 assert((b1 + BigFixed(2, 10)).toDecimalString(2) == "3.00"); 293 assert((b1 * BigFixed(2, 10)).toDecimalString(2) == "2.00"); 294 assert((b1 - BigFixed(1, 10)).toDecimalString(2) == "0.00"); 295 } 296 }