1 module math.loopnum; 
2 
3 import std.traits;
4 
5 
6 /// Inclusing min and exclusive max.  Effectively max will equal min, min & max will be the joining number in the loop, 360deg = 0deg, `LoopNum(int,360)(360)==LoopNum(int,360)(0)`.
7 alias LoopNum(T, T max) = LoopNum!(T, 0, max);
8 struct LoopNum(T, T min, T max) // For signed integrals: `max == T.min` is interpreted as `max == T.max+1`.
9 if (min<max || isIntegral!T && isSigned!T && max==T.min)
10 {
11 	static assert (!(isIntegral!T && isSigned!T && min==T.min), "For signed integrals: `min` cannot be `T.min` because `max-min` must fit in UT and `max==T.min` is interpreted as `T.max+1`.");
12 	
13 	alias This = typeof(this);
14 	static if (isIntegral!T)
15 		alias UT = Unsigned!T;
16 	else
17 		alias UT = T;
18 	
19 	T num=min;
20 	
21 	T opCast(T:T)() {
22 		return num;
23 	}
24 	
25 	this (T init) {
26 		num = fix(init);
27 	}
28 	
29 	static T fix(T n) {
30 		if (n >= min)
31 			return min + (cast(UT) n-min) % (cast(UT) max-min);
32 		else
33 			return max - (cast(UT) min-n) % (cast(UT) max-min);
34 	}
35 	
36 	This opBinary(string op:"+")(T rhs) {
37 		static if (isIntegral!T && isSigned!T)
38 			assert(rhs!=T.min);
39 		if (rhs < 0)
40 			return this - (-rhs);
41 		if (rhs < cast(UT) max-num)
42 			return This(num+rhs);
43 		else
44 			return This(min + (rhs - (cast(UT) max-num)) % (cast(UT) max-min));
45 	}
46 	This opBinary(string op:"-")(T rhs) {
47 		static if (isIntegral!T && isSigned!T)
48 			assert(rhs!=T.min);
49 		if (rhs < 0)
50 			return this + (-rhs);
51 		if (rhs <= cast(UT) num-min)
52 			return This(num-rhs);
53 		else
54 			return This(max - (rhs - (cast(UT) num-min)) % (cast(UT) max-min));
55 	}
56 	
57 	bool opEquals(This rhs) {
58 		return num == rhs.num;
59 	}
60 }
61 
62 
63 unittest {
64 	import std.meta;
65 	{
66 		foreach(T; AliasSeq!(int, uint, float)) {
67 			alias L = LoopNum!(T,100);
68 			assert(L(5) + 5 == L(10));
69 			assert(L(50) + 50 == L(0));
70 			assert(L(50) + 51 == L(1));
71 			assert(L(51) + 50 == L(1));
72 			assert(L(51) - 50 == L(1));
73 			assert(L(50) - 51 == L(99));
74 			assert(L(51) + 150 == L(1));
75 			assert(L(51) - 150 == L(1));
76 		}
77 		foreach(T; AliasSeq!(int, float)) {
78 			alias L = LoopNum!(T,100);
79 			assert(L(5) - (-5) == L(10));
80 			assert(L(50) - (-50) == L(0));
81 			assert(L(50) - (-51) == L(1));
82 			assert(L(51) - (-50) == L(1));
83 			assert(L(51) + (-50) == L(1));
84 			assert(L(50) + (-51) == L(99));
85 			assert(L(51) - (-150) == L(1));
86 			assert(L(51) + (-150) == L(1));
87 		}
88 	}
89 	{
90 		foreach(T; AliasSeq!(int, uint, float)) {
91 			alias L = LoopNum!(T,10,100);
92 			assert(L(15) + 5 == L(20));
93 			assert(L(50) + 50 == L(10));
94 			assert(L(50) + 51 == L(11));
95 			assert(L(51) + 50 == L(11));
96 			assert(L(51) - 40 == L(11));
97 			assert(L(50) - 41 == L(99));
98 			assert(L(51) + 140 == L(11));
99 			assert(L(51) - 130 == L(11));
100 		}
101 		foreach(T; AliasSeq!(int, float)) {
102 			alias L = LoopNum!(T,10,100);
103 			assert(L(15) - (-5) == L(20));
104 			assert(L(50) - (-50) == L(10));
105 			assert(L(50) - (-51) == L(11));
106 			assert(L(51) - (-50) == L(11));
107 			assert(L(51) + (-40) == L(11));
108 			assert(L(50) + (-41) == L(99));
109 			assert(L(51) - (-140) == L(11));
110 			assert(L(51) + (-130) == L(11));
111 		}
112 	}
113 	{
114 		foreach(T; AliasSeq!(int, float)) {
115 			alias L = LoopNum!(T,-10,100);
116 			assert(L(-5) + 5 == L(0));
117 			assert(L(50) + 50 == L(-10));
118 			assert(L(50) + 51 == L(-9));
119 			assert(L(51) + 50 == L(-9));
120 			assert(L(51) - 60 == L(-9));
121 			assert(L(50) - 61 == L(99));
122 			assert(L(51) + 160 == L(-9));
123 			assert(L(51) - 170 == L(-9));
124 			
125 			assert(L(-5) - (-5) == L(0));
126 			assert(L(50) - (-50) == L(-10));
127 			assert(L(50) - (-51) == L(-9));
128 			assert(L(51) - (-50) == L(-9));
129 			assert(L(51) + (-60) == L(-9));
130 			assert(L(50) + (-61) == L(99));
131 			assert(L(51) - (-160) == L(-9));
132 			assert(L(51) + (-170) == L(-9));
133 		}
134 	}
135 	{
136 		foreach(T; AliasSeq!(int)) {
137 			alias L = LoopNum!(T, T.min+1, T.max+1);
138 			assert(L(5) + 1 == L(6));
139 			assert(L(T.max).num == T.max);
140 			assert(L(T.min) == L(T.max));
141 			assert(L(T.min+1) - 1 == L(T.max));
142 			assert(L(T.max) + 1 == L(T.min+1));
143 			assert(L(T.min+1) + T.max == L(0));
144 			assert(L(T.max) - T.max == L(0));
145 			assert(L(T.min+1) - T.max == L(1));
146 			assert(L(T.max) + T.max == L(-1));
147 			assert(L(T.min/2) - T.min/2 == L(0));
148 		}
149 	}
150 }
151