1 module nxt.modulo;
2 
3 import std.traits : isIntegral, isSigned;
4 
5 /** Module type within inclusive value range [0 .. `m`-1].
6 
7 	Similar to Ada's modulo type `0 mod m`.
8 
9 	See_Also: https://forum.dlang.org/post/hmrpwyqfoxwtywbznbrr@forum.dlang.org
10 	See_Also: http://codeforces.com/contest/628/submission/16212299
11 
12 	TODO: reuse ideas from bound.d
13 
14 	TODO: Add function limit()
15 	static if (isPowerOf2!m)
16 	{
17 	return x & 2^^m - 1;
18 	}
19 	else
20 	{
21 	return x % m;
22 	}
23 
24 	invariant
25 	{
26 		assert (0 <= x && x < m);
27 	}
28 
29 	called after opBinary opUnary etc similar to what is done
30 	http://codeforces.com/contest/628/submission/16212299
31 
32 	TODO: Move to Phobos std.typecons
33  */
34 template Mod(size_t m, T = UnsignedOfModulo!m)
35 if (m >= 1 &&
36 	isIntegral!T)
37 {
38 	// smallest builtin unsigned integer type that can fit 2^^m
39 	alias UI = UnsignedOfModulo!m;
40 
41 	static assert(m - 1 <= 2^^(8*T.sizeof) - 1); // if so, check that it matches `s`
42 
43 	// `this` uses exactly as many bits as storage type `T`
44 	enum exactStorageTypeMatch = m == 2^^(T.sizeof);
45 
46 	struct Mod
47 	{
48 		enum min = 0;
49 		enum max = m - 1;
50 
51 		/// Construct from `value` of unsigned integer type `UI`.
52 		this(U)(U value) if (isIntegral!U)
53 		in
54 		{
55 			static if (m != 2^^(U.sizeof)) // dynamic check only modulo doesn't equal storage precision
56 			{
57 				static if (isSigned!U)
58 					assert(value >= 0, `value too small`);
59 				assert(value < m, `value too large`);
60 			}
61 		}
62 		do
63 		{
64 			this._value = cast(T)value;
65 		}
66 
67 		/// Construct from Mod!n, where `n <= m`.
68 		this(size_t n, U)(Mod!(n, U) rhs)
69 		if (n <= m && isIntegral!U)
70 		{
71 			this._value = cast(T)rhs._value; // cannot overflow
72 		}
73 
74 		/// Assign from `value` of unsigned integer type `UI`.
75 		auto ref opAssign(U)(U value) if (isIntegral!U)
76 		in
77 		{
78 			static if (m != 2^^(U.sizeof)) // dynamic check only modulo doesn't equal storage precision
79 			{
80 				static if (isSigned!U)
81 					assert(value >= 0, `value too small`);
82 				assert(value < m, `value too large`);
83 			}
84 		}
85 		do
86 		{
87 			this._value = cast(T)value; // overflow checked in ctor
88 		}
89 
90 		/// Assign from Mod!n, where `n <= m`.
91 		auto ref opAssign(size_t n, U)(Mod!(n, U) rhs)
92 		if (n <= m && isIntegral!U)
93 		{
94 			this._value = cast(T)rhs._value; // cannot overflow
95 		}
96 
97 		auto ref opOpAssign(string op, U)(U rhs)
98 		if ((op == `+` ||
99 			 op == `-` ||
100 			 op == `*`) &&
101 			isIntegral!U)
102 		{
103 			mixin(`_value = cast(T)(_value ` ~ op ~ `rhs);`);
104 			return this;
105 		}
106 
107 		auto opUnary(string op, string file = __FILE__, int line = __LINE__)()
108 		{
109 			static	  if (op == `+`)
110 			{
111 				return this;
112 			}
113 			else static if (op == `--`)
114 			{
115 				import nxt.math_ex : isPowerOf2;
116 				static if (isPowerOf2(m))
117 					_value = (_value - 1) & max; // more efficient
118 				else
119 					if (_value == min) _value = max; else --_value;
120 				return this;
121 			}
122 			else static if (op == `++`)
123 			{
124 				import nxt.math_ex : isPowerOf2;
125 				static if (isPowerOf2(m))
126 					_value = (_value + 1) & max; // more efficient
127 				else
128 					if (_value == max) _value = min; else ++_value;
129 				return this;
130 			}
131 			else
132 				static assert(`Unsupported unary operator `, op);
133 		}
134 
135 		@property size_t _prop() const pure nothrow @safe @nogc { return _value; } // read-only access
136 		alias _prop this;
137 
138 		private T _value;
139 	}
140 }
141 
142 /// Instantiator for `Mod`.
143 auto mod(size_t m, T = UnsignedOfModulo!m)(T value)
144 if (m >= 1)
145 {
146 	return Mod!(m)(value);
147 }
148 
149 /** Lookup type representing an unsigned integer in inclusive range (0 .. m - 1).
150  *
151  * TODO: Merge with similar logic in bound.d
152  */
153 private template UnsignedOfModulo(size_t m)
154 {
155 	static	  if (m - 1 <= ubyte.max)
156 	{
157 		alias UnsignedOfModulo = ubyte;
158 	}
159 	else static if (m - 1 <= ushort.max)
160 	{
161 		alias UnsignedOfModulo = ushort;
162 	}
163 	else static if (m - 1 <= uint.max)
164 	{
165 		alias UnsignedOfModulo = uint;
166 	}
167 	else
168 	{
169 		alias UnsignedOfModulo = ulong;
170 	}
171 	/+ TODO: ucent? +/
172 }
173 
174 ///
175 pure nothrow @safe @nogc unittest {
176 	enum m = 256;
177 	Mod!(m, ubyte) ub = cast(ubyte)(m - 1);
178 	Mod!(m, uint) ui = cast(ubyte)(m - 1);
179 	assert(ub == ui);
180 	--ub;
181 	assert(ub != ui);
182 	ui = ub;
183 	assert(ub == ui);
184 	--ui;
185 	assert(ub != ui);
186 	ub = ui;
187 	assert(ub == ui);
188 }
189 
190 ///
191 pure nothrow @safe @nogc unittest {
192 	static assert(is(typeof(Mod!3(1)) ==
193 					 typeof(1.mod!3)));
194 
195 	// check size logic
196 	static assert(Mod!(ubyte.max + 1).sizeof == 1);
197 	static assert(Mod!(ubyte.max + 2).sizeof == 2);
198 	static assert(Mod!(ushort.max + 1).sizeof == 2);
199 	static assert(Mod!(ushort.max + 2).sizeof == 4);
200 	static assert(Mod!(cast(size_t)uint.max + 1).sizeof == 4);
201 	static assert(Mod!(cast(size_t)uint.max + 2).sizeof == 8);
202 
203 	// assert that storage defaults to packed unsigned integer
204 	static assert(is(Mod!(8, ubyte) == Mod!(8)));
205 
206 	Mod!(8, ubyte) x = 6;
207 	static assert(x.min == 0);
208 	static assert(x.max == 7);
209 	Mod!(8, ubyte) y = 7;
210 	static assert(y.min == 0);
211 	static assert(y.max == 7);
212 
213 	assert(x < y);
214 
215 	y = 5;
216 	y = 5L;
217 
218 	assert(y < x);
219 
220 	assert(y == 5);
221 	assert(y != 0);
222 
223 	y++;
224 	assert(y == 6);
225 	assert(++y == y.max);
226 	assert(y++ == y.max);
227 	assert(y == y.min);
228 	assert(--y == y.max);
229 	assert(++y == y.min);
230 
231 	Mod!(8, uint) ui8 = 7;
232 	Mod!(256, ubyte) ub256 = 255;
233 	ub256 = ui8;	// can assign to larger modulo from higher storage precision
234 
235 	Mod!(258, ushort) ub258 = ub256;
236 
237 	// copy construction to smaller modulo is disallowed
238 	static assert(!__traits(compiles, { Mod!(255, ubyte) ub255 = ub258; }));
239 
240 	auto a = 7.mod!10;
241 	auto b = 8.mod!256;
242 	auto c = 257.mod!1000;
243 
244 	static assert(c.min == 0);
245 	static assert(c.max == 999);
246 
247 	assert(a < b);
248 	assert(a < c);
249 
250 	// assignment to larger modulo (super-type/set) is allowed
251 	b = a;
252 	c = a;
253 	c = b;
254 
255 	// assignment to smaller modulo (sub-type/set) is disallowed
256 	static assert(!__traits(compiles, { a = b; }));
257 	static assert(!__traits(compiles, { a = c; }));
258 }
259 
260 ///
261 pure nothrow @safe @nogc unittest {
262 	Mod!(256, ubyte) x = 55;
263 
264 	x += 200;
265 	assert(x == 255);
266 
267 	x += 1;
268 	assert(x == 0);
269 
270 	x -= 4;
271 	assert(x == 252);
272 }
273 
274 /// construct from other precision
275 pure nothrow @safe @nogc unittest {
276 	enum m = 256;
277 	Mod!(m, ubyte) x = 55;
278 	Mod!(m, uint) y = x;
279 	Mod!(m, ubyte) z = y;
280 }