1 //Written in the D programming language 2 /* 3 * Fixed point type. 4 * 5 * Copyright 2013-2015 Jaypha 6 * 7 * Distributed under the Boost Software License, Version 1.0. 8 * (See http://www.boost.org/LICENSE_1_0.txt) 9 * 10 * Authors: Jason den Dulk 11 * Contributers: MoodleLadyCow, Pablo De Nápoli 12 */ 13 14 /* 15 * A fixed point number is a number with a fixed number of decimal places. The 16 * number of decimal places never varies, unlike floating point types, where the 17 * number of decimal places varies depending on the value. 18 * 19 * Fixed point values are used wherever fractions are needed, but floating point 20 * values are undesirable or impractical, eg currencies. 21 * 22 * Fixed point values are precise (no rounding issues) and are integral in 23 * behaviour (division and modulo work the same as they do for integers). 24 */ 25 26 module jaypha.fixed; 27 28 import std.string; 29 import std.math; 30 import std.conv; 31 import std.traits; 32 import std.stdio; 33 import std.regex; 34 import std.format; 35 36 //----------------------------------------------------------------------------- 37 struct Fixed(uint scale) 38 //----------------------------------------------------------------------------- 39 { 40 enum factor = 10^^scale; 41 //enum pattern = ctRegex!(format!"^[0-9]+(.[0-9]{1,%s})?$"(scale)); <-- This would be preferred, except it breaks when scale = 0. 42 enum pattern = ctRegex!("^[+,-]?[0-9]+(\\.[0-9]+)?$"); 43 44 private: 45 enum sc = scale; 46 long value; 47 static pure nothrow Fixed make(long v) { Fixed fixed; fixed.value = v; return fixed; } 48 49 // Converts from a string to a long usable by this class. 50 static long fromString_(string v) 51 { 52 if (matchFirst(v, pattern).empty) 53 throw new Exception(format("Fixed!%s: Input string '%s' is invalid",scale,v)); 54 55 auto words = split(v, "."); 56 auto tmp = to!long(words[0]) * factor; 57 if (words.length > 1) { 58 if (words[1].length > scale) 59 throw new Exception(format("Fixed!%s: Input string '%s' is invalid",scale,v)); 60 auto fractionPart = to!long(words[1]) * 10^^(scale-words[1].length); 61 if (tmp < 0) 62 tmp -= fractionPart; 63 else 64 tmp += fractionPart; 65 } 66 return tmp; 67 } 68 69 public: 70 71 // min and max represent the smallest and larget possible values respectively. 72 73 static immutable Fixed min = make(long.min); 74 static immutable Fixed max = make(long.max); 75 76 //----------------------------------------------------- 77 78 pure nothrow this(long v) { value = v * factor; } 79 nothrow this(double v) { value = lround(v * factor); } 80 this(string v) { value = fromString_(v); } 81 82 83 84 //----------------------------------------------------------------------------- 85 // integralPart & decimalPart 86 87 nothrow long integralPart() const 88 { 89 return value/factor; 90 } 91 nothrow long integralPart(long newIntegralPart) 92 { 93 if (newIntegralPart<0) 94 value = newIntegralPart * factor + decimalPart*-1; 95 else 96 value = newIntegralPart * factor + decimalPart; 97 return newIntegralPart; 98 } 99 100 /++ 101 + this returns |x| - ⌊|x|⌋, so it's always positive 102 +/ 103 nothrow long decimalPart() const 104 { 105 return abs(value%factor); 106 } 107 /++ 108 + sign doesn't matter, for instance those two calls are equivalent: 109 + - fix2(-3).decimalPart = -14 // produces -3.14 110 + - fix2(-3).decimalPart = 14 // same 111 + 112 + if the argument is too big, it is truncated 113 +/ 114 nothrow long decimalPart(long newDecimalPart) 115 { 116 if ((value < 0) != (newDecimalPart < 0)) 117 newDecimalPart *= -1; 118 while (newDecimalPart > factor) 119 newDecimalPart /= 10; 120 value = integralPart * factor + newDecimalPart; 121 return newDecimalPart; 122 } 123 124 //----------------------------------------------------- 125 // This function was added so that vibe recognize Fixed as 126 // string serializable. 127 // See: http://vibed.org/api/vibe.data.serialization/isStringSerializable 128 //----------------------------------------------------- 129 130 static Fixed!scale fromString(string v){ return Fixed!scale(v);} 131 132 133 //----------------------------------------------------- 134 // Unary operators. 135 136 pure nothrow Fixed opUnary(string s:"++")() { value += factor; return this; } 137 pure nothrow Fixed opUnary(string s:"--")() { value -= factor; return this; } 138 139 pure nothrow Fixed opUnary(string s)() { mixin("return make("~s~"value);"); } 140 141 //----------------------------------------------------- 142 // Equality 143 144 pure nothrow bool opEquals(Fixed b) const { return (value == b.value); } 145 pure nothrow bool opEquals(long b) const { return (value == b*factor); } 146 pure nothrow bool opEquals(double b) const { return ((cast(double)value / factor) == b); } 147 148 //----------------------------------------------------- 149 // Comparison 150 151 pure nothrow int opCmp(const Fixed b) const 152 { 153 if (value < b.value) return -1; 154 if (value > b.value) return 1; 155 return 0; 156 } 157 158 pure nothrow int opCmp(const long b) const 159 { 160 if (value < b*factor) return -1; 161 if (value > b*factor) return 1; 162 return 0; 163 } 164 165 pure nothrow int opCmp(const double b) const 166 { 167 if (value < b*factor) return -1; 168 if (value > b*factor) return 1; 169 return 0; 170 } 171 172 //----------------------------------------------------- 173 // Assignment 174 175 pure nothrow void opAssign(Fixed v) { value = v.value; } 176 pure nothrow void opAssign(long v) { value = v * factor; } 177 void opAssign(double v) { value = to!long(v * factor); } 178 void opAssign(string v) { value = fromString_(v); } 179 180 //----------------------------------------------------- 181 // Operator assignment 182 183 pure nothrow void opOpAssign(string op)(Fixed v) 184 { 185 mixin("value = (this "~op~" v).value;"); 186 } 187 188 pure nothrow void opOpAssign(string op)(long v) 189 { 190 mixin("value = (this "~op~" v).value;"); 191 } 192 nothrow void opOpAssign(string op)(double v) 193 { 194 mixin("value = (this "~op~" v).value;"); 195 } 196 197 //----------------------------------------------------- 198 // Cast and conversion 199 200 pure nothrow T opCast(T : long)() const 201 { return integralPart; } 202 203 pure nothrow T opCast(T : double)() const 204 { return (cast(double)value) / factor; } 205 206 pure nothrow T opCast(T : bool)() const 207 { return value != 0; } 208 209 // If newScale is less, then the value is rounded. 210 pure nothrow auto conv(uint newScale)() const 211 { 212 static if (newScale == scale) 213 return Fixed!scale.make(value); 214 else static if (newScale > scale) 215 return Fixed!newScale.make(value * 10^^(newScale-scale)); 216 else 217 { 218 auto div = 10^^(scale-newScale); 219 auto newValue = value / div; 220 if (value%div >= div/2) 221 ++newValue; 222 return Fixed!newScale.make(newValue); 223 } 224 } 225 226 pure @property string asString() const 227 { 228 return toString(); 229 } 230 231 pure string toString() const 232 { 233 if (value == long.min || abs(value) >= factor) 234 return format("%s.%0*d",std.conv.to!string(integralPart),scale,decimalPart); 235 else 236 { 237 string sign = value >= 0 ? "" : "-"; 238 return format("%s0.%0*d",sign,scale,abs(value)); 239 } 240 } 241 242 243 //----------------------------------------------------- 244 // Operators for Fixed and Fixed 245 246 pure nothrow Fixed opBinary(string s:"+")(Fixed b) const 247 { 248 return make(value+b.value); 249 } 250 251 pure nothrow Fixed opBinary(string s:"-")(Fixed b) const 252 { 253 return make(value-b.value); 254 } 255 256 pure nothrow Fixed opBinary(string s:"*")(Fixed b) const 257 { 258 return make(value*b.value/factor); 259 } 260 261 pure nothrow Fixed opBinary(string s:"/")(Fixed b) const 262 { 263 return make((value*factor)/b.value); 264 } 265 266 //----------------------------------------------------- 267 // Operators for Fixed and long 268 269 pure nothrow Fixed opBinary(string s:"+")(long b) const 270 { 271 return make(value + b*factor); 272 } 273 pure nothrow Fixed opBinaryRight(string s:"+")(long b) const 274 { 275 return make(value + b*factor); 276 } 277 pure nothrow Fixed opBinary(string s:"-")(long b) const 278 { 279 return make(value - b*factor); 280 } 281 pure nothrow Fixed opBinaryRight(string s:"-")(long b) const 282 { 283 return make(b*factor - value); 284 } 285 pure nothrow Fixed opBinary(string s:"*")(long b) const 286 { 287 return make(value*b); 288 } 289 pure nothrow Fixed opBinaryRight(string s:"*")(long b) const 290 { 291 return make(b*value); 292 } 293 pure nothrow Fixed opBinary(string s:"/")(long b) const 294 { 295 return make(value/b); 296 } 297 pure nothrow Fixed opBinaryRight(string s:"/")(long b) const 298 { 299 return make((b*factor*factor)/value); 300 } 301 pure nothrow Fixed opBinary(string s:"%")(long b) const 302 { 303 return make(value%(b*factor)); 304 } 305 pure nothrow Fixed opBinaryRight(string s:"%")(long b) const 306 { 307 return make((b*factor)%value); 308 } 309 310 311 //----------------------------------------------------- 312 // Operators for Fixed and double 313 314 nothrow Fixed opBinary(string s:"+")(double b) const 315 { 316 return make(value + lround(b*factor)); 317 } 318 nothrow Fixed opBinaryRight(string s:"+")(double b) const 319 { 320 return make(value + lround(b*factor)); 321 } 322 nothrow Fixed opBinary(string s:"-")(double b) const 323 { 324 return make(value - lround(b*factor)); 325 } 326 nothrow Fixed opBinaryRight(string s:"-")(double b) const 327 { 328 return make(lround(b*factor) - value); 329 } 330 nothrow Fixed opBinary(string s:"*")(double b) const 331 { 332 return make(lround(value*b)); 333 } 334 nothrow Fixed opBinaryRight(string s:"*")(double b) const 335 { 336 return make(lround(b*value)); 337 } 338 nothrow Fixed opBinary(string s:"/")(double b) const 339 { 340 return make(lround(value/b)); 341 } 342 nothrow Fixed opBinaryRight(string s:"/")(double b) const 343 { 344 return make(lround((b*factor*factor)/value)); 345 } 346 } 347 348 //----------------------------------------------------------------------------- 349 // T1 and T2 must be instances of Fixed. 350 351 pure nothrow auto mult(T1, T2)(T1 op1, T2 op2) 352 if (__traits(isSame,TemplateOf!(T1), Fixed) && 353 __traits(isSame,TemplateOf!(T2), Fixed)) 354 { 355 return Fixed!(T1.sc+T2.sc).make(op1.value*op2.value); 356 } 357 358 //----------------------------------------------------------------------------- 359 360 alias Fixed!1 fix1; 361 alias Fixed!2 fix2; 362 alias Fixed!3 fix3; 363 364 //----------------------------------------------------------------------------- 365 366 unittest 367 { 368 //import std.stdio; 369 370 // Fundamentals 371 372 assert(fix1.factor == 10); 373 assert(fix2.factor == 100); 374 assert(fix3.factor == 1000); 375 assert(fix1.min.value == long.min); 376 assert(fix1.max.value == long.max); 377 assert(fix2.min.value == long.min); 378 assert(fix2.max.value == long.max); 379 assert(fix3.min.value == long.min); 380 assert(fix3.max.value == long.max); 381 382 // Default 383 384 fix2 amount; 385 assert(amount.value == 0); 386 assert(amount.toString() == "0.00"); 387 assert(amount.asString == "0.00"); 388 389 // Creation 390 391 fix1 v1 = 14; 392 assert(v1.value == 140); 393 fix2 v2 = -23.45; 394 assert(v2.value == -2345); 395 fix3 v3 = "134"; 396 assert(v3.value == 134000); 397 fix3 v4 = "134.5"; 398 assert(v4.value == 134500); 399 fix3 v5 = "134.55"; 400 assert(v5.value == 134550); 401 fix3 v6 = "134.557"; 402 assert(v6.value == 134557); 403 404 auto v7 = fix1("22"); 405 assert(v7.value == 220); 406 407 assert(fix1(62).value == 620); 408 assert(fix2(-30).value == -3000); 409 assert(fix3("120").value == 120000); 410 assert(fix2(24.6).value == 2460); 411 assert(fix2(-27.2).value == -2720); 412 assert(fix2(16.1f).value == 1610); 413 assert(fix2(-87.3f).value == -8730); 414 415 int i1 = 23; 416 v2 = i1; 417 assert(v2.value == 2300); 418 419 i1 = -15; 420 v1 = i1; 421 assert(v1.value == -150); 422 423 long l1 = 435; 424 v2 = l1; 425 assert(v2.value == 43500); 426 427 l1 = -222; 428 v3 = l1; 429 assert(v3.value == -222000); 430 431 // Assignment 432 433 amount = 20; 434 assert(amount.value == 2000); 435 amount = -30L; 436 assert(amount.value == -3000); 437 amount = 13.6f; 438 assert(amount.value == 1360); 439 amount = 7.3; 440 assert(amount.value == 730); 441 amount = "-30.7"; 442 assert(amount.value == -3070); 443 amount = "200.06"; 444 assert(amount.value == 20006); 445 446 try { 447 // Cannot assign a non-number 448 amount = "xyz"; 449 // Cannot assign a number with more decimal places than the scale. 450 amount = "-30.787"; 451 assert(false); 452 } catch (Exception e) { 453 } 454 455 // Comparison operators 456 457 amount = 30; 458 459 assert(amount == 30); 460 assert(amount != 22); 461 assert(amount <= 30); 462 assert(amount >= 30); 463 assert(amount > 29); 464 assert(!(amount > 31)); 465 assert(amount < 31); 466 assert(!(amount < 29)); 467 468 amount = 22.34; 469 470 assert(amount == 22.34); 471 assert(amount != 15.6); 472 assert(amount <= 22.34); 473 assert(amount >= 22.34); 474 assert(amount > 22.33); 475 assert(!(amount > 22.35)); 476 assert(amount < 22.35); 477 assert(!(amount < 22.33)); 478 479 fix2 another = 22.34; 480 assert(amount == another); 481 assert(amount <= another); 482 assert(amount >= another); 483 484 another = 22.35; 485 assert(amount != another); 486 assert(amount < another); 487 assert(amount <= another); 488 assert(!(amount > another)); 489 assert(!(amount >= another)); 490 assert(another > amount); 491 assert(another >= amount); 492 assert(!(another < amount)); 493 assert(!(another <= amount)); 494 495 // Cast and conversion 496 497 amount = 22; 498 long lVal = cast(long)amount; 499 assert(lVal == 22); 500 double dVal = cast(double)amount; 501 assert(dVal == 22.0); 502 assert(amount.toString() == "22.00"); 503 assert(fix2(0.15).toString() == "0.15"); 504 assert(fix2(-0.02).toString() == "-0.02"); 505 assert(fix2(-43.6).toString() == "-43.60"); 506 assert(fix2.min.toString() == "-92233720368547758.08"); 507 assert(fix2.max.toString() == "92233720368547758.07"); 508 bool bVal = cast(bool)amount; 509 assert(bVal == true); 510 assert(amount); 511 assert(!fix2(0)); 512 513 auto cv1 = amount.conv!1(); 514 assert(cv1.factor == 10); 515 assert(cv1.value == 220); 516 auto cv3 = amount.conv!3(); 517 assert(cv3.factor == 1000); 518 assert(cv3.value == 22000); 519 520 fix3 amt3 = 3.752; 521 auto amt2 = amt3.conv!2(); 522 assert(amt2.factor == 100); 523 assert(amt2.value == 375); 524 auto amt1 = amt3.conv!1(); 525 assert(amt1.factor == 10); 526 assert(amt1.value == 38); 527 auto amt0 = amt3.conv!0(); 528 assert(amt0.factor == 1); 529 assert(amt0.value == 4); 530 531 // Arithmmetic operators 532 533 fix2 op1, op2; 534 535 op1 = 5.23; 536 op2 = 7.1; 537 538 assert((op1 + op2) == 12.33); 539 assert((op1 - op2) == -1.87); 540 assert((op1 * op2) == 37.13); 541 assert((op1 / op2) == 0.73); 542 543 assert(op1 + 10 == 15.23); 544 assert(op1 - 10 == -4.77); 545 assert(op1 * 10 == 52.3); 546 assert(op1 / 10 == 0.52); 547 assert(op1 % 10 == 5.23); 548 549 assert(10 + op1 == 15.23); 550 assert(10 - op1 == 4.77); 551 assert(10 * op1 == 52.3); 552 assert(10 / op1 == 1.91); 553 assert(10 % op1 == 4.77); 554 555 assert(op1 + 9.8 == 15.03); 556 assert(op1 - 9.8 == -4.57); 557 assert(op1 * 9.8 == 51.25); 558 assert(op1 / 9.8 == 0.53); 559 560 assert(9.8 + op1 == 15.03); 561 assert(9.8 - op1 == 4.57); 562 assert(9.8 * op1 == 51.25); 563 564 assert(9.8 / op1 == 1.87); 565 566 assert(op1 != op2); 567 assert(op1 == fix2(5.23)); 568 assert(op2 == fix2("7.1")); 569 assert(op2 != fix2("7.09")); 570 assert(op2 != fix2("7.11")); 571 572 // Increment, decrement 573 574 amount = 20; 575 assert(++amount == 21); 576 assert(amount == 21); 577 assert(--amount == 20); 578 assert(amount == 20); 579 assert(-amount == -20); 580 assert(amount == 20); 581 582 amount = amount + 14; 583 assert(amount.value == 3400); 584 585 amount = 6 + amount; 586 assert(amount.value == 4000); 587 588 // Assignment operators. 589 590 amount = 40; 591 592 amount += 5; 593 assert(amount.value == 4500); 594 595 amount -= 6.5; 596 assert(amount.value == 3850); 597 598 another = -4; 599 600 amount += another; 601 assert(amount.value == 3450); 602 603 amount *= 1.5; 604 assert(amount.value == 5175); 605 606 amount /= 12; 607 assert(amount.value == 431); 608 609 assert(Fixed!2.fromString("2.5") == Fixed!2("2.5")); 610 611 // The following template is copied from vibe.d sources 612 // Copyright (c) 2012-2018 RejectedSoftware e.K. 613 // which is permited by vibe.d licence (MIT public license, 614 // see http://vibed.org/about#license) 615 // in order to test the fromString method in the exact way that vibe.d does 616 617 template isStringSerializable(T) 618 { 619 enum bool isStringSerializable = is(typeof(T.init.toString()) : string) && is(typeof(T.fromString("")) : T); 620 } 621 622 alias number= Fixed!2; 623 static assert(isStringSerializable!number); 624 625 // More tests. 626 627 amount = 0.05; 628 assert(amount.value == 5); 629 assert(amount == 0.05); 630 assert(amount.toString() == "0.05"); 631 assert(cast(long)amount == 0); 632 assert(cast(double)amount == 0.05); 633 634 amount = 1.05; 635 assert(amount.value == 105); 636 assert(amount == 1.05); 637 assert(amount.toString() == "1.05"); 638 assert(cast(long)amount == 1); 639 assert(cast(double)amount == 1.05); 640 641 assert((++amount).value == 205); 642 assert(amount.value == 205); 643 assert((-amount).value == -205); 644 assert(--amount == 1.05); 645 assert(amount.value == 105); 646 647 amount = 50; 648 assert(amount.value == 5000); 649 650 651 another = amount * 2; 652 assert(another.value == 10000); 653 amount *= 3; 654 assert(amount.value == 15000); 655 656 amount = "30"; 657 assert(amount.value == 3000); 658 659 amount = 295; 660 amount /= 11; 661 assert(amount.value == 2681); 662 assert(amount == 26.81); 663 664 amount = 295; 665 another = 11; 666 assert((amount/another).value == 2681); 667 assert((amount/another).toString() == "26.81"); 668 669 another = amount + 1.3; 670 assert(another.value == 29630); 671 672 amount = 30; 673 another = 50.2 - amount; 674 assert(another.value == 2020); 675 another -= 50; 676 677 assert(another.value == -2980); 678 assert(another == -29.8); 679 680 another = amount/1.6; 681 assert(another.value == 1875); 682 683 another = amount*1.56; 684 assert(another.value == 4680); 685 686 fix1 a = 3.2; 687 fix2 b = 1.15; 688 689 assert(a.value == 32); 690 assert(b.value == 115); 691 692 assert(fix2(334) / 15 == 22.26); 693 assert(fix2(334) % 10 == 4); 694 695 assert(334 / fix2(15.3) == 21.83); 696 assert(334 % fix2(15.3) == 12.7); 697 698 // mult function 699 700 fix2 _v1 = 1.27; 701 fix2 _v2 = 3.45; 702 auto _v = _v1.mult(_v2); 703 assert(_v.sc == 4); 704 assert(_v.value == 43815); 705 706 707 // integralPart & decimalPart 708 { 709 fix2 aa = 123.45; 710 assert(aa.integralPart == 123); 711 assert(aa.decimalPart == 45); 712 713 aa = -98765.43; 714 assert(aa.integralPart == -98765); 715 assert(aa.decimalPart == 43); 716 717 aa.integralPart = 0; 718 assert(aa == fix2(0.43)); 719 aa.integralPart(aa.integralPart - 1); 720 aa.integralPart = aa.integralPart - 1; 721 assert(aa.integralPart == -2); 722 assert(aa.decimalPart == 43); 723 724 aa.decimalPart = 99; 725 assert(aa == fix2(-2.99)); 726 aa.decimalPart = -98; 727 assert(aa == fix2(-2.98)); 728 aa.integralPart = 5; 729 assert(aa == fix2(5.98)); 730 } 731 }