1 module fixedpoint.fixed; 2 3 import std.conv : to, parse; 4 import std.algorithm : splitter; 5 import std.range : repeat, chain, empty; 6 import std.format : format, formattedWrite; 7 import std..string : split; 8 import std.math : sgn, abs, round; 9 import std.traits : isIntegral, isFloatingPoint, isNumeric, isNarrowString, hasMember; 10 11 //TODO opCmp & opEals between different Fixed types, modulo, power, ceil, floor, init test, documentation&examples 12 13 /// Fixed Class 14 struct Fixed(int scaling, V = long, Hook = KeepScalingHook) if (isIntegral!V) 15 { 16 /// Value of the Fixed 17 V value = V.init; 18 /// Factor of the scaling 19 enum factor = 10 ^^ scaling; 20 /// Smalest Fixed 21 static immutable Fixed min = make(V.min); 22 /// Largest Fixed 23 static immutable Fixed max = make(V.max); 24 25 /// Create a new Fixed, given an integral 26 this(const V i) 27 { 28 value = i * factor; 29 } 30 /// 31 unittest 32 { 33 auto p1 = Fixed!4(1); 34 assert(p1.value == 1 * 10 ^^ 4); 35 36 auto p2 = Fixed!5(1); 37 assert(p2.value == 1 * 10 ^^ 5); 38 } 39 40 /// Create a new Fixed give a floating point number 41 this(T)(const T i) if (isFloatingPoint!T) 42 { 43 value = (i * factor).round.to!V; 44 } 45 /// 46 unittest 47 { 48 auto p = Fixed!4(1.1); 49 assert(p.value == 1.1 * 10 ^^ 4); 50 } 51 52 /// Create a new Fixed struct given a string. If round is true, then the number is rounded to the nearest signficiant digit. 53 this(Range)(Range s, bool round = false) if (isNarrowString!Range) 54 { 55 if (!s.empty) 56 { 57 import std.range.primitives; 58 auto spl = s.splitter("."); 59 auto frontItem = spl.front; 60 spl.popFront; 61 bool neg = frontItem.length > 0 && frontItem.front == '-'; 62 typeof(frontItem) decimal; 63 typeof(frontItem) truncatedDecimal; 64 if (!spl.empty) 65 decimal = spl.front; 66 if (decimal.length > scaling) 67 truncatedDecimal = decimal[0 .. scaling]; // truncate 68 else 69 truncatedDecimal = decimal; 70 value = chain(frontItem, truncatedDecimal, '0'.repeat(scaling - truncatedDecimal.length)).to!V; 71 if(round && decimal.length > scaling && decimal[scaling] >= '5') 72 { 73 if(neg) 74 --value; 75 else 76 ++value; 77 } 78 } 79 } 80 /// 81 unittest 82 { 83 auto p1 = Fixed!4("1.1"); 84 assert(p1.value == 11_000); 85 86 auto p2 = Fixed!4("1."); 87 assert(p2.value == 10_000); 88 89 auto p3 = Fixed!4("0"); 90 assert(p3.value == 0); 91 92 auto p4 = Fixed!4(""); 93 assert(p4.value == 0); 94 95 auto p5 = Fixed!3("1.2345", true); 96 assert(p5.value == 1235); 97 } 98 99 /// Direct construction of a Fixed struct 100 static pure nothrow Fixed make(const V v) 101 { 102 Fixed fixed; 103 fixed.value = v; 104 return fixed; 105 } 106 /// 107 unittest 108 { 109 auto p1 = Fixed!3.make(1); 110 assert(p1.value == 1); 111 112 auto p2 = Fixed!3(1); 113 assert(p2.value == 1000); 114 } 115 116 Fixed opAssign(const Fixed p) 117 { 118 value = p.value; 119 return this; 120 } 121 122 Fixed opAssign(T)(const T n) if (isNumeric!T) 123 { 124 value = to!V(n * factor); 125 return this; 126 } 127 128 Fixed opAssign(string s) 129 { 130 value = Fixed(s).value; 131 return this; 132 } 133 134 Fixed opOpAssign(string op, T)(const T n) 135 { 136 // just forward to opBinary. 137 return this = opBinary!op(n); 138 } 139 140 string toString() const 141 { 142 string sign = value.sgn == -1 ? "-" : ""; 143 return format!"%s%d.%0*d"(sign, (value / factor).abs, scaling, (value % factor).abs); 144 } 145 146 void toString(Out)(auto ref Out outRange) const if (is(typeof(formattedWrite!"test"(outRange)))) 147 { 148 string sign = value.sgn == -1 ? "-" : ""; 149 outRange.formattedWrite!"%s%d.%0*d"(sign, (value / factor).abs, scaling, (value % factor).abs); 150 } 151 152 /// Creating Fixed from a string, needed by vibed: http://vibed.org/api/vibe.data.serialization/isStringSerializable 153 static Fixed fromString(string v) 154 { 155 return Fixed(v); 156 } 157 158 bool opEquals(const Fixed other) const 159 { 160 return other.value == value; 161 } 162 163 bool opEquals(point)(const Fixed!point other) const 164 { 165 return other.value * other.factor == value * factor; 166 } 167 168 bool opEquals(T)(const T other) const if (isIntegral!T) 169 { 170 return value == other * factor; 171 } 172 173 int opCmp(const Fixed other) const 174 { 175 if (value < other.value) 176 return -1; 177 else if (value > other.value) 178 return 1; 179 else 180 return 0; 181 } 182 183 int opCmp(T)(const T other) const if (isNumeric!T) 184 { 185 if (value < other * factor) 186 return -1; 187 else if (value > other * factor) 188 return 1; 189 else 190 return 0; 191 } 192 193 unittest 194 { 195 auto p = Fixed!4(3); 196 assert(p == Fixed!4(3)); 197 //assert(p == 3.0); 198 assert(p == 3); 199 assert(p == Fixed!4(3)); 200 auto p2 = Fixed!4(2); 201 assert(p > p2); 202 assert(p2 < p); 203 } 204 205 Fixed opUnary(string s)() if (s == "--" || s == "++") 206 { 207 mixin("value" ~ s[0] ~ "= factor;"); 208 return this; 209 } 210 211 Fixed opUnary(string s)() const if (s == "-" || s == "+") 212 { 213 auto f = Fixed(); 214 f.value = mixin(s ~ "value"); 215 return f; 216 } 217 218 unittest 219 { 220 auto p = Fixed!1(1.1); 221 assert(to!string(p) == "1.1"); 222 p++; 223 ++p; 224 assert(p.value == 31); 225 assert((-p).value == -31); 226 } 227 228 T opCast(T : Fixed!(point, U), int point, U)() const 229 { 230 static if (point > scaling) 231 return T.make(value.to!U * (10 ^^ (point - scaling))); 232 else static if (point < scaling) 233 return T.make(value.to!U / (10 ^^ (scaling - point))); 234 else 235 return this; 236 } 237 238 T opCast(T : bool)() const 239 { 240 return value != 0; 241 } 242 243 T opCast(T)() const if (isNumeric!T) 244 { 245 return (value.to!T / factor).to!T; 246 } 247 248 unittest 249 { 250 auto p = Fixed!4(1.1); 251 assert(cast(int) p == 1); 252 assert(p.to!int == 1); 253 assert(p.to!double == 1.1); 254 assert(p.to!(Fixed!5).value == 110_000); 255 } 256 257 /// 258 auto opBinary(string op, Rhs)(const Rhs rhs) const 259 if (isIntegral!Rhs || isFloatingPoint!Rhs || is(Rhs == bool)) 260 { 261 static if (hasMember!(Hook, "hookOpBinaryNumeric")) 262 return hook.hookOpBinaryNumeric!op(this, rhs); 263 else static if (is(Rhs == bool)) 264 return mixin("this" ~ op ~ "ubyte(rhs)"); 265 else static if (isFloatingPoint!Rhs) 266 return mixin("this.to!Rhs" ~ op ~ "rhs"); 267 else static if (isIntegral!Rhs) 268 { 269 static if (op == "+" || op == "-") 270 return Fixed.make(mixin("value" ~ op ~ "(rhs * factor)")); 271 else static if (op == "*" || op == "/") 272 return Fixed.make(mixin("value" ~ op ~ "rhs")); 273 else 274 static assert(0, "Operation " ~ op ~ " is not (yet) implemented"); 275 } 276 } 277 278 /// 279 auto opBinary(string op, int S, W, H)(Fixed!(S, W, H) rhs) const 280 { 281 static if (is(Hook == H) && hasMember!(H, "hookOpBinaryFixed")) 282 return Hook.hookOpBinaryFixed!(op, scaling, V, Hook, S, W, H)(this, rhs); 283 else static if (hasMember!(Hook, "hookOpBinaryFixed") 284 && !hasMember!(H, "hookOpBinaryFixed")) 285 return Hook.hookOpBinaryFixed!op(this, rhs); 286 else static if (!hasMember!(Hook, "hookOpBinaryFixed") 287 && hasMember!(H, "hookOpBinaryFixed")) 288 return H.hookOpBinaryFixed!op(this, rhs); 289 else 290 static assert(0, "Conflict between Hooks"); 291 } 292 293 /// 294 auto opBinaryRight(string op, Lhs)(const Lhs lhs) const 295 if (isIntegral!Lhs || isFloatingPoint!Lhs || is(Lhs == bool)) 296 { 297 static if (hasMember!(Hook, "hookOpBinaryRightNumeric")) 298 return hook.hookOpBinaryRightNumeric!op(lhs, this); 299 else static if (is(Lhs == bool)) 300 return mixin("ubyte(rhs)" ~ op ~ "this"); 301 else static if (isFloatingPoint!Lhs) 302 return mixin("lhs" ~ op ~ "this.to!Lhs"); 303 else static if (isIntegral!Lhs) 304 { 305 static if (op == "+" || op == "-") 306 return Fixed.make(mixin("(lhs * factor)" ~ op ~ "value")); 307 else static if (op == "*") 308 return Fixed.make(mixin("lhs" ~ op ~ "value")); 309 else static if (op == "/") 310 return Fixed.make(mixin("(lhs * factor * factor)" ~ op ~ "value")); 311 else 312 static assert(0, "Operation " ~ op ~ " is not (yet) implemented"); 313 } 314 } 315 316 /// 317 unittest 318 { 319 auto p1 = Fixed!4(1); 320 auto p2 = Fixed!4(2); 321 assert(p1 + 1 == 2); 322 assert(p1 + p2 == 3); 323 assert(p1 * 2 == 2); 324 assert(cast(double)(p1 / 2) == 0.5); 325 auto p3 = Fixed!2(2); 326 auto p4 = p2 + p3.to!(Fixed!4); 327 assert(p4 == 4); 328 } 329 330 size_t toHash() const 331 { 332 return value.hashOf; 333 } 334 } 335 336 /// 337 struct KeepScalingHook 338 { 339 /// 340 static auto hookOpBinaryFixed(string op, int scaling, V, Hook, int S, W, H)( 341 const Fixed!(scaling, V, Hook) lhs, const Fixed!(S, W, H) rhs) 342 if (op == "+" || op == "-" || op == "*" || op == "/") 343 { 344 static if (is(typeof(lhs) == typeof(rhs))) 345 { 346 static if (op == "+" || op == "-") 347 return typeof(lhs).make(mixin("lhs.value" ~ op ~ "rhs.value")); 348 else static if (op == "*") 349 return typeof(lhs).make(((lhs.value * rhs.value) / lhs.factor).round.to!V); 350 else static if (op == "/") 351 return typeof(lhs).make(((lhs.value * lhs.factor) / rhs.value).round.to!V); 352 } 353 else 354 { 355 static if (scaling > S) 356 alias nextScaling = scaling; 357 else 358 alias nextScaling = S; 359 static if (V.sizeof > W.sizeof) 360 alias nextV = V; 361 else 362 alias nextV = W; 363 alias common = Fixed!(nextScaling, nextV, Hook); 364 return mixin("lhs.to!common" ~ op ~ "rhs.to!common"); 365 } 366 } 367 } 368 369 /// 370 struct MoveScalingHook 371 { 372 /// 373 static auto hookOpBinaryFixed(string op, int scaling, V, Hook, int S, W, H)( 374 const Fixed!(scaling, V, Hook) lhs, const Fixed!(S, W, H) rhs) 375 if (op == "+" || op == "-" || op == "*" || op == "/") 376 { 377 static if (is(typeof(lhs) == typeof(rhs))) 378 { 379 static if (op == "+" || op == "-") 380 return typeof(lhs).make(mixin("lhs.value" ~ op ~ "rhs.value")); 381 else static if (op == "*") 382 return Fixed!(2 * scaling, V, Hook).make((lhs.value * rhs.value).round.to!V); 383 else static if (op == "/") 384 return Fixed!(0, V, Hook).make((lhs.value / rhs.value).round.to!V); 385 } 386 else 387 { 388 static if (V.sizeof > W.sizeof) 389 alias nextV = V; 390 else 391 alias nextV = W; 392 393 static if (op == "+" || op == "-") 394 { 395 static if (scaling > S) 396 alias nextScaling = scaling; 397 else 398 alias nextScaling = S; 399 alias common = Fixed!(nextScaling, nextV, Hook); 400 return mixin("lhs.to!common" ~ op ~ "rhs.to!common"); 401 } 402 else static if (op == "*") 403 { 404 return Fixed!(scaling + S, nextV, Hook).make(value * rhs.value); 405 return Fixed!(scaling - S, nextV, Hook).make(value / rhs.value); 406 407 } 408 } 409 } 410 } 411 412 unittest 413 { 414 import std.stdio : writeln; 415 import std.math : isClose; 416 import std.exception : assertThrown; 417 418 alias fix1 = Fixed!1; 419 alias fix2 = Fixed!2; 420 alias fix3 = Fixed!3; 421 422 // Fundamentals 423 424 assert(fix1.factor == 10); 425 assert(fix2.factor == 100); 426 assert(fix3.factor == 1000); 427 assert(fix1.min.value == long.min); 428 assert(fix1.max.value == long.max); 429 assert(fix2.min.value == long.min); 430 assert(fix2.max.value == long.max); 431 assert(fix3.min.value == long.min); 432 assert(fix3.max.value == long.max); 433 434 // Default 435 436 fix2 amount; 437 assert(amount.value == 0); 438 assert(amount.toString() == "0.00"); 439 440 // Creation 441 442 fix1 v1 = 14; 443 assert(v1.value == 140); 444 fix2 v2 = -23.45; 445 assert(v2.value == -2345); 446 fix3 v3 = "134"; 447 assert(v3.value == 134_000); 448 fix3 v4 = "134.5"; 449 assert(v4.value == 134_500); 450 451 auto v5 = fix1("22"); 452 assert(v5.value == 220); 453 454 assert(fix1(62).value == 620); 455 assert(fix2(-30).value == -3000); 456 assert(fix3("120").value == 120_000); 457 assert(fix3("120".dup).value == 120_000); 458 assert(fix3("120.123").value == 120_123); 459 assert(fix3("120.1234").value == 120_123); 460 assert(fix3("120.1", true).value == 120_100); 461 assert(fix3("120.12", true).value == 120_120); 462 assert(fix3("120.123", true).value == 120_123); 463 assert(fix3("120.1234", true).value == 120_123); 464 assert(fix3("120.1235", true).value == 120_124); 465 assert(fix3("120.12349", true).value == 120_123); 466 assert(fix3("120.12359", true).value == 120_124); 467 assert(fix3("120.12340", true).value == 120_123); 468 assert(fix3("120.12350", true).value == 120_124); 469 assertThrown(Fixed!10("12345678901234567")); 470 assert(fix2(24.6).value == 2460); 471 assert(fix2(-27.2).value == -2720); 472 assert(fix2(16.1f).value == 1610); 473 assert(fix2(-87.3f).value == -8730); 474 475 // test negative rounding 476 assert(fix2("-1.005", true).value == -101); 477 478 int i1 = 23; 479 v2 = i1; 480 assert(v2.value == 2300); 481 482 i1 = -15; 483 v1 = i1; 484 assert(v1.value == -150); 485 486 long l1 = 435; 487 v2 = l1; 488 assert(v2.value == 43_500); 489 490 l1 = -222; 491 v3 = l1; 492 assert(v3.value == -222_000); 493 494 // Assignment 495 496 amount = 20; 497 assert(amount.value == 2000); 498 amount = -30L; 499 assert(amount.value == -3000); 500 amount = 13.6f; 501 assert(amount.value == 1360); 502 amount = 7.3; 503 assert(amount.value == 730); 504 amount = "-30.7"; 505 assert(amount.value == -3070); 506 507 // Comparison operators 508 509 amount = 30; 510 511 assert(amount == 30); 512 assert(amount != 22); 513 assert(amount <= 30); 514 assert(amount >= 30); 515 assert(amount > 29); 516 assert(!(amount > 31)); 517 assert(amount < 31); 518 assert(!(amount < 29)); 519 520 amount = 22.34; 521 522 assert(amount.value == 2234); 523 assert(amount != 1560); 524 assert(amount <= 22.34); 525 assert(amount >= 22.34); 526 assert(amount > 22.33); 527 assert(!(amount > 22.35)); 528 assert(amount < 22.35); 529 assert(!(amount < 22.33)); 530 531 fix2 another = 22.34; 532 assert(amount == another); 533 assert(amount <= another); 534 assert(amount >= another); 535 536 another = 22.35; 537 assert(amount != another); 538 assert(amount < another); 539 assert(amount <= another); 540 assert(!(amount > another)); 541 assert(!(amount >= another)); 542 assert(another > amount); 543 assert(another >= amount); 544 assert(!(another < amount)); 545 assert(!(another <= amount)); 546 547 // Cast and conversion 548 549 amount = 22; 550 long lVal = cast(long) amount; 551 assert(lVal == 22); 552 double dVal = cast(double) amount; 553 assert(dVal == 22.0); 554 assert(dVal == amount.to!double); 555 assert(dVal * dVal == amount.to!double * amount.to!double); 556 assert(amount.toString() == "22.00"); 557 assert(fix2(0.15).toString() == "0.15"); 558 assert(fix2(-0.02).toString() == "-0.02"); 559 assert(fix2(-43.6).toString() == "-43.60"); 560 assert(fix2.min.toString() == "-92233720368547758.08"); 561 assert(fix2.max.toString() == "92233720368547758.07"); 562 bool bVal = cast(bool) amount; 563 assert(bVal == true); 564 assert(amount); 565 assert(!fix2(0)); 566 567 auto cv1 = amount.to!(Fixed!1); 568 assert(cv1.factor == 10); 569 assert(cv1.value == 220); 570 auto cv3 = amount.to!(Fixed!3); 571 assert(cv3.factor == 1000); 572 assert(cv3.value == 22_000); 573 574 fix3 amt3 = 3.752; 575 auto amt2 = amt3.to!(Fixed!2); 576 assert(amt2.factor == 100); 577 assert(amt2.value == 375); 578 auto amt1 = amt3.to!(Fixed!1); 579 assert(amt1.factor == 10); 580 assert(amt1.value == 37); 581 auto amt0 = amt3.to!(Fixed!0); 582 assert(amt0.factor == 1); 583 assert(amt0.value == 3); 584 585 // Arithmmetic operators 586 587 fix2 op1, op2; 588 fix3 op3; 589 590 op1 = 5.23; 591 op2 = 7.1; 592 op3 = 1.337; 593 594 assert((op1 + op2).value == 1233); 595 assert((op1 - op2).value == -187); 596 assert((op1 * op2).value == 3713); 597 assert((op1 / op2).value == 73); 598 assert((op3 + op2).value == 8437); 599 assert((op3 - op2).value == -5763); 600 assert((op3 * op2).value == 9492); 601 assert((op3 / op2).value == 188); 602 assert((op2 + op3).value == 8437); 603 assert((op2 + op3).value == (op3 + op2).value); 604 assert((op2 - op3).value == 5763); 605 assert((op2 - op3).value == -(op3 - op2).value); 606 assert((op2 * op3).value == 9492); 607 assert((op2 * op3).value == (op3 * op2).value); 608 assert((op2 / op3).value == 5310); 609 610 assert((op1 + 10).value == 1523); 611 assert((op1 - 10).value == -477); 612 assert((op1 * 10).value == 5230); 613 assert((op1 / 10).value == 52); 614 //assert(op1 % 10 == 5.23); 615 616 assert((10 + op1).value == 1523); 617 assert((10 - op1).value == 477); 618 assert((10 * op1).value == 5230); 619 assert((10 / op1).value == 191); 620 //assert(10 % op1 == 4.77); 621 622 assert(isClose(op1 + 9.8, 15.03)); 623 assert(isClose(op1 - 9.8, -4.57)); 624 assert(isClose(op1 * 9.8, 51.254)); 625 assert(isClose(op1 / 9.8, 0.53367346939)); 626 627 assert(isClose(9.8 + op1, 15.03)); 628 assert(isClose(9.8 - op1, 4.57)); 629 assert(isClose(9.8 * op1, 51.254)); 630 assert(isClose(9.8 / op1, 1.87380497132)); 631 632 assert(op1 != op2); 633 assert(op1 == fix2(5.23)); 634 assert(op2 == fix2("7.1")); 635 assert(op2 != fix2("7.09")); 636 assert(op2 != fix2("7.11")); 637 638 // Increment, decrement 639 640 amount = 20; 641 assert(++amount == 21); 642 assert(amount == 21); 643 assert(--amount == 20); 644 assert(amount == 20); 645 assert(-amount == -20); 646 assert(amount == 20); 647 648 amount = amount + 14; 649 assert(amount.value == 3400); 650 651 amount = 6 + amount; 652 assert(amount.value == 4000); 653 654 // Assignment operators. 655 656 amount = 40; 657 658 amount += 5; 659 assert(amount.value == 4500); 660 661 amount -= 6.5; 662 assert(amount.value == 3850); 663 664 another = -4; 665 666 amount += another; 667 assert(amount.value == 3450); 668 669 amount *= 1.5; 670 assert(amount.value == 5175); 671 672 amount /= 12; 673 assert(amount.value == 431); 674 675 assert(Fixed!2.fromString("2.5") == Fixed!2("2.5")); 676 677 // The following template is copied from vibe.d sources 678 // Copyright (c) 2012-2018 RejectedSoftware e.K. 679 // which is permited by vibe.d licence (MIT public license, 680 // see http://vibed.org/about#license) 681 // in order to test the fromString method in the exact way that vibe.d does 682 683 template isStringSerializable(T) 684 { 685 enum bool isStringSerializable = is(typeof(T.init.toString()) : string) 686 && is(typeof(T.fromString("")) : T); 687 } 688 689 alias number = Fixed!2; 690 static assert(isStringSerializable!number); 691 692 // More tests. 693 694 amount = 0.05; 695 assert(amount.value == 5); 696 assert(amount.toString() == "0.05"); 697 assert(cast(long) amount == 0); 698 assert(cast(double) amount == 0.05); 699 700 amount = 1.05; 701 assert(amount.value == 105); 702 assert(amount.toString() == "1.05"); 703 assert(cast(long) amount == 1); 704 assert(cast(double) amount == 1.05); 705 706 assert((++amount).value == 205); 707 assert(amount.value == 205); 708 assert((-amount).value == -205); 709 assert((--amount).value == 105); 710 assert(amount.value == 105); 711 712 amount = 50; 713 assert(amount.value == 5000); 714 715 another = amount * 2; 716 assert(another.value == 10_000); 717 amount *= 3; 718 assert(amount.value == 15_000); 719 720 amount = "30"; 721 assert(amount.value == 3000); 722 723 amount = 295; 724 amount /= 11; 725 assert(amount.value == 2681); 726 assert(amount.to!double == 26.81); 727 728 amount = 295; 729 another = 11; 730 assert((amount / another).value == 2681); 731 assert((amount / another).toString() == "26.81"); 732 733 another = amount + 1.3; 734 assert(another.value == 29_630); 735 736 amount = 30; 737 another = 50.2 - amount; 738 assert(another.value == 2020); 739 another -= 50; 740 741 assert(another.value == -2980); 742 743 another = amount / 1.6; 744 assert(another.value == 1875); 745 746 another = amount * 1.56; 747 assert(another.value == 4680); 748 749 fix1 a = 3.2; 750 fix2 b = 1.15; 751 752 assert(a.value == 32); 753 assert(b.value == 115); 754 755 assert((fix2(334) / 15).value == 2226); 756 //assert(fix2(334) % 10 == 4); 757 758 assert((334 / fix2(15.3)).value == 2183); 759 //assert(334 % fix2(15.3) == 12.7); 760 } 761 762 // test safe toString 763 @safe unittest 764 { 765 Fixed!2 val = Fixed!2(100.5); 766 string result; 767 void addToResult(const(char)[] v) 768 { 769 result ~= v; 770 } 771 val.toString(&addToResult); 772 assert(result == "100.50"); 773 }