1 /** 2 Decimal module. 3 */ 4 module karasux.decimal; 5 6 import std.algorithm : max; 7 import std.ascii : isDigit; 8 import std.math : floor, log10, abs; 9 import std.string : format; 10 import std.traits : isFloatingPoint; 11 import std.typecons : Nullable, nullable, Tuple, tuple; 12 13 @safe: 14 15 /** 16 Decimal type. 17 */ 18 struct Decimal 19 { 20 /** 21 Min price number. 22 */ 23 static immutable(Decimal) min = Decimal(long.min, ubyte.min); 24 25 /** 26 price number. 27 */ 28 static immutable(Decimal) max = Decimal(long.max, ubyte.max); 29 30 /** 31 Number mantissa. 32 */ 33 long mantissa; 34 35 /** 36 Number exponent. 37 */ 38 ubyte exponent; 39 40 /** 41 Add exponent and shift mantissa. 42 43 Params: 44 n = shift value. 45 Returns: 46 shifted number. 47 */ 48 Decimal addExponent(ubyte n) const @nogc nothrow pure 49 in ((cast(uint) exponent) + n <= ubyte.max) 50 out (r; (mantissa > 0) ? r.mantissa >= mantissa : r.mantissa <= mantissa) 51 { 52 if (n == 0) 53 { 54 return this; 55 } 56 57 return Decimal(mantissa * (10 ^^ n), cast(ubyte)(exponent + n)); 58 } 59 60 /// 61 @nogc nothrow pure unittest 62 { 63 auto price = Decimal(123456, 3); 64 assert(price.addExponent(0) == Decimal(123456, 3)); 65 assert(price.addExponent(1) == Decimal(1234560, 4)); 66 assert(price.addExponent(2) == Decimal(12345600, 5)); 67 } 68 69 /// 70 @nogc nothrow pure unittest 71 { 72 auto price = Decimal(-123456, 3); 73 assert(price.addExponent(0) == Decimal(-123456, 3)); 74 assert(price.addExponent(1) == Decimal(-1234560, 4)); 75 assert(price.addExponent(2) == Decimal(-12345600, 5)); 76 } 77 78 /** 79 Compare other price number. 80 81 Params: 82 other = other value. 83 Returns: 84 true if equal value. 85 */ 86 bool opEquals()(auto scope ref const(Decimal) other) const @nogc nothrow pure 87 { 88 if (exponent == other.exponent) 89 { 90 return mantissa == other.mantissa; 91 } 92 93 return this.normalized.mantissa == other.normalized.mantissa; 94 } 95 96 /// 97 @nogc nothrow pure unittest 98 { 99 immutable a = Decimal(123456, 3); 100 assert(a == a); 101 102 immutable b = Decimal(1234560, 4); 103 assert(a == b); 104 105 assert(a != Decimal(123457, 3)); 106 assert(a != Decimal(1234567, 4)); 107 } 108 109 /** 110 Returns: 111 value hash. 112 */ 113 size_t toHash() const @nogc nothrow pure @safe 114 { 115 immutable n = normalized; 116 return (31 * n.mantissa) ^ (131 * n.exponent); 117 } 118 119 /// 120 @nogc nothrow pure unittest 121 { 122 assert(Decimal(12300, 2).toHash == Decimal(12300, 2).toHash); 123 assert(Decimal(12300, 2).toHash == Decimal(123000, 3).toHash); 124 assert(Decimal(12300, 2).toHash != Decimal(12300, 1).toHash); 125 } 126 127 /** 128 Compare two prices. 129 130 Params: 131 other = other price number. 132 Returns: 133 opCmp result. 134 */ 135 int opCmp()(auto ref const(Decimal) other) const @nogc nothrow pure scope 136 { 137 if (exponent == other.exponent) 138 { 139 return mantissa.cmp(other.mantissa); 140 } 141 142 immutable m = matchExponent(this, other); 143 return m[0].mantissa.cmp(m[1].mantissa); 144 } 145 146 /// 147 @nogc nothrow pure unittest 148 { 149 immutable a = Decimal(123456, 3); 150 assert(!(a < a)); 151 assert(!(a > a)); 152 assert(a <= a); 153 assert(a >= a); 154 155 immutable b = Decimal(123457, 3); 156 assert(a < b); 157 assert(a <= b); 158 assert(!(a > b)); 159 assert(!(a >= b)); 160 161 assert(b > a); 162 assert(b >= a); 163 assert(!(b < a)); 164 assert(!(b <= a)); 165 166 immutable c = Decimal(1234560, 4); 167 assert(!(a < c)); 168 assert(!(a > c)); 169 assert(a <= c); 170 assert(a >= c); 171 172 assert(!(c < a)); 173 assert(!(c > a)); 174 assert(c <= a); 175 assert(c >= a); 176 177 immutable d = Decimal(1234561, 4); 178 assert(a < d); 179 assert(!(a > d)); 180 assert(a <= d); 181 assert(!(a >= d)); 182 183 assert(!(d < a)); 184 assert(d > a); 185 assert(!(d <= a)); 186 assert(d >= a); 187 } 188 189 /// 190 @nogc nothrow pure unittest 191 { 192 immutable a = Decimal(1234559, 4); // 123.4559 193 immutable b = Decimal(123456, 3); // 123.456 194 assert(a < b); 195 assert(a <= b); 196 assert(!(b < a)); 197 198 assert(b > a); 199 assert(b >= a); 200 assert(!(a > b)); 201 } 202 203 /** 204 Calculate other price. 205 206 Params: 207 rhs = other hand price. 208 Returns: 209 calculated price. 210 */ 211 Decimal opBinary(string op)(auto scope ref const(Decimal) rhs) const @nogc nothrow pure 212 if (op == "+" || op == "-") 213 { 214 immutable matched = matchExponent(this, rhs); 215 immutable resultMantissa = mixin("matched[0].mantissa " ~ op ~ "matched[1].mantissa"); 216 return Decimal(resultMantissa, matched[0].exponent); 217 } 218 219 /// 220 @nogc nothrow pure unittest 221 { 222 immutable a = Decimal(123000, 3); 223 immutable b = Decimal(456, 3); 224 assert(a + b == Decimal(123456, 3)); 225 assert(a - b == Decimal(122544, 3)); 226 227 immutable c = Decimal(-456, 3); 228 assert(a + c == Decimal(122544, 3)); 229 assert(a - c == Decimal(123456, 3)); 230 231 assert(b - a == Decimal(-122544, 3)); 232 } 233 234 /** 235 Calculate mul and div. 236 237 Params: 238 rhs = other hand value. 239 Returns: 240 calculated price. 241 */ 242 Decimal opBinary(string op)(long rhs) const @nogc nothrow pure 243 if (op == "*" || op == "/") 244 { 245 return Decimal(mixin("mantissa " ~ op ~ " rhs"), exponent); 246 } 247 248 /// 249 @nogc nothrow pure unittest 250 { 251 immutable a = Decimal(123456, 3); 252 assert(a * 2 == Decimal(246912, 3)); 253 assert(a / 2 == Decimal(123456 / 2, 3)); 254 } 255 256 /** 257 Calculate mul. 258 259 Params: 260 rhs = other hand value. 261 Returns: 262 calculated price. 263 */ 264 Decimal opBinary(string op)(auto scope ref const(Decimal) rhs) const @nogc nothrow pure 265 if (op == "*") 266 { 267 return Decimal(mantissa * rhs.mantissa, cast(ubyte)(exponent + rhs.exponent)); 268 } 269 270 /// 271 @nogc nothrow pure unittest 272 { 273 immutable a = Decimal(123456, 3); 274 assert(a * Decimal(2, 0) == Decimal(246912, 3)); 275 assert(a * Decimal(2, 1) == Decimal(246912, 4)); 276 } 277 278 /** 279 Convert from floating point. 280 281 Params: 282 value = floating point value. 283 exponent = price number exponent. 284 Returns: 285 price number. 286 */ 287 static Decimal from(T)(auto scope ref const(T) value, ubyte exponent) @nogc nothrow pure @safe 288 if (isFloatingPoint!T) 289 { 290 return Decimal(cast(long) .floor(value * (10.0 ^^ exponent) + 0.5), exponent); 291 } 292 293 /// 294 @nogc nothrow pure @safe unittest 295 { 296 assert(Decimal.from(123.456, 3) == Decimal(123456, 3)); 297 assert(Decimal.from(123.001, 3) == Decimal(123001, 3)); 298 assert(Decimal.from(123.0019, 3) == Decimal(123002, 3)); 299 300 assert(Decimal.from(-123.456, 3) == Decimal(-123456, 3)); 301 assert(Decimal.from(-123.001, 3) == Decimal(-123001, 3)); 302 assert(Decimal.from(-123.0019, 3) == Decimal(-123002, 3)); 303 } 304 305 /** 306 Convert to floating point. 307 308 Params: 309 price = price value. 310 Returns: 311 floating point value. 312 */ 313 auto opCast(T)() const @nogc nothrow pure scope 314 if (isFloatingPoint!T) 315 { 316 return (cast(T) mantissa) / (cast(T) 10.0) ^^ exponent; 317 } 318 319 /// 320 @nogc nothrow pure unittest 321 { 322 import std.math : approxEqual; 323 assert(approxEqual(cast(double) Decimal(123456, 0), 123456.0)); 324 assert(approxEqual(cast(double) Decimal(123456, 3), 123.456)); 325 assert(approxEqual(cast(double) Decimal(-123456, 3), -123.456)); 326 } 327 328 /** 329 Returns: 330 String representation. 331 */ 332 string toString() const pure 333 { 334 if (exponent == 0) 335 { 336 return format("%d", mantissa); 337 } 338 339 immutable unit = 10L ^^ exponent; 340 immutable m = mantissa / unit; 341 immutable f = abs(mantissa % unit); 342 return format("%d.%0*d", m, exponent, f); 343 } 344 345 /// 346 pure unittest 347 { 348 assert(Decimal(123456, 3).toString == "123.456"); 349 assert(Decimal(123000, 3).toString == "123.000"); 350 assert(Decimal(123456, 0).toString == "123456"); 351 assert(Decimal(123456, 6).toString == "0.123456"); 352 assert(Decimal(1, 3).toString == "0.001"); 353 assert(Decimal(123001, 3).toString == "123.001"); 354 assert(Decimal(-123001, 3).toString == "-123.001", Decimal(-123001, 3).toString); 355 } 356 357 /** 358 Parse price number from string. 359 360 Params: 361 s = target string 362 Returns: 363 Decimal if succceeded. 364 */ 365 static Nullable!Decimal fromString(scope const(char)[] s) nothrow @nogc pure @safe 366 { 367 if (s.length == 0) 368 { 369 return typeof(return).init; 370 } 371 372 // read sign. 373 bool plus = true; 374 size_t start = 0; 375 if (s[0] == '-') 376 { 377 plus = false; 378 ++start; 379 } 380 else if (s[0] == '+') 381 { 382 ++start; 383 } 384 385 ulong n = 0; 386 ptrdiff_t dotIndex = s.length; 387 foreach (i, c; s[start .. $]) 388 { 389 if (c == '.') 390 { 391 dotIndex = start + i; 392 continue; 393 } 394 395 if (!c.isDigit) 396 { 397 return typeof(return).init; 398 } 399 400 n *= 10; 401 n += (c - '0'); 402 } 403 404 immutable exponent = .max( 405 0, (cast(ptrdiff_t) s.length) - dotIndex - 1); 406 if (exponent > ubyte.max) 407 { 408 return typeof(return).init; 409 } 410 411 return Decimal(plus ? n : -n, cast(ubyte) exponent).nullable; 412 } 413 414 /// 415 nothrow pure @safe unittest 416 { 417 import std.exception : assertThrown; 418 import std.range : array, repeat; 419 420 assert(Decimal.fromString(".123456") == Decimal(123456, 6)); 421 assert(Decimal.fromString("123.456") == Decimal(123456, 3)); 422 assert(Decimal.fromString("12345.6") == Decimal(123456, 1)); 423 assert(Decimal.fromString("123456.") == Decimal(123456, 0)); 424 assert(Decimal.fromString("123456") == Decimal(123456, 0)); 425 assert(Decimal.fromString("0.001") == Decimal(1, 3)); 426 assert(Decimal.fromString("100.000") == Decimal(100000, 3)); 427 428 assert(Decimal.fromString("a123456").isNull); 429 assert(Decimal.fromString("123.456a").isNull); 430 431 string zeros = '0'.repeat(ubyte.max - 1).array; 432 assert(Decimal.fromString("." ~ zeros ~ "1") == Decimal(1, ubyte.max)); 433 assert(Decimal.fromString("." ~ zeros ~ "01").isNull); 434 435 assert(Decimal.fromString("+123.456") == Decimal(+123456, 3)); 436 assert(Decimal.fromString("-123.456") == Decimal(-123456, 3)); 437 assert(Decimal.fromString("+.123456") == Decimal(123456, 6)); 438 assert(Decimal.fromString("-.123456") == Decimal(-123456, 6)); 439 } 440 441 /** 442 Calcurate floor value. 443 444 Returns: 445 floor value. 446 */ 447 Decimal floor() const @nogc nothrow pure scope 448 { 449 immutable scale = (cast(long) 10) ^^ exponent; 450 return Decimal(mantissa / scale, 0); 451 } 452 453 /// 454 @nogc nothrow pure @safe unittest 455 { 456 assert(Decimal(10, 1).floor == Decimal(1, 0)); 457 assert(Decimal(11, 1).floor == Decimal(1, 0)); 458 assert(Decimal(10001, 4).floor == Decimal(1, 0)); 459 assert(Decimal(20001, 4).floor == Decimal(2, 0)); 460 } 461 462 /** 463 Calcurate ceil value. 464 465 Returns: 466 ceil value. 467 */ 468 Decimal ceil() const @nogc nothrow pure scope 469 { 470 immutable scale = (cast(long) 10) ^^ exponent; 471 immutable rest = mantissa % scale; 472 return Decimal((mantissa / scale) + (rest > 0 ? 1 : 0), 0); 473 } 474 475 /// 476 @nogc nothrow pure @safe unittest 477 { 478 assert(Decimal(10, 1).ceil == Decimal(1, 0)); 479 assert(Decimal(11, 1).ceil == Decimal(2, 0)); 480 assert(Decimal(10001, 4).ceil == Decimal(2, 0)); 481 } 482 483 /** 484 Normalize mantissa and exponent. 485 486 Returns: 487 normalized value. 488 */ 489 @property Decimal normalized() const @nogc nothrow pure scope 490 { 491 Decimal result = this; 492 while (result.mantissa % 10 == 0 && result.exponent > 0) 493 { 494 result.mantissa /= 10; 495 --result.exponent; 496 } 497 498 return result; 499 } 500 501 /// 502 @nogc nothrow pure @safe unittest 503 { 504 assert(Decimal.init.normalized.mantissa == 0); 505 assert(Decimal.init.normalized.exponent == 0); 506 507 assert(Decimal(100, 0).normalized.mantissa == 100); 508 assert(Decimal(100, 0).normalized.exponent == 0); 509 510 assert(Decimal(100, 2).normalized.mantissa == 1); 511 assert(Decimal(100, 2).normalized.exponent == 0); 512 513 assert(Decimal(120, 2).normalized.mantissa == 12); 514 assert(Decimal(120, 2).normalized.exponent == 1); 515 516 assert(Decimal(123, 2).normalized.mantissa == 123); 517 assert(Decimal(123, 2).normalized.exponent == 2); 518 519 assert(Decimal(12340000, 2).normalized.mantissa == 123400); 520 assert(Decimal(12340000, 2).normalized.exponent == 0); 521 } 522 } 523 524 /** 525 Match two exponent. 526 527 Params: 528 a = price number 1. 529 b = price number 2. 530 Returns: 531 matched exponent. 532 */ 533 auto matchExponent()( 534 auto scope ref const(Decimal) a, 535 auto scope ref const(Decimal) b) @nogc nothrow pure 536 out(r; r[0].exponent == r[1].exponent) 537 { 538 immutable maxExponent = max(a.exponent, b.exponent); 539 return tuple( 540 a.addExponent(cast(ubyte)(maxExponent - a.exponent)), 541 b.addExponent(cast(ubyte)(maxExponent - b.exponent))); 542 } 543 544 /// 545 @nogc nothrow pure unittest 546 { 547 immutable a = Decimal(123456, 3); 548 assert(matchExponent(a, a) == tuple(a, a)); 549 550 immutable b = Decimal(7890120, 4); 551 assert(matchExponent(a, b) == tuple(Decimal(1234560, 4), b)); 552 assert(matchExponent(b, a) == tuple(b, Decimal(1234560, 4))); 553 } 554 555 private: 556 557 int cmp(T)(auto ref const(T) a, auto ref const(T) b) 558 { 559 if (a < b) 560 { 561 return -1; 562 } 563 564 if (a > b) 565 { 566 return 1; 567 } 568 569 return 0; 570 } 571 572 /// 573 @nogc nothrow pure unittest 574 { 575 assert(cmp(0, 0) == 0); 576 assert(cmp(1, 0) == 1); 577 assert(cmp(0, 1) == -1); 578 } 579