1 module moggle.math.normalized;
2 
3 import std.algorithm;
4 
5 /// An integral type behaving like a floating point type.
6 struct Normalized(T) {
7 
8 pure:
9 nothrow:
10 
11 	static if (T.max < 1 << float.mant_dig) {
12 		private alias float F;
13 	} else static if (T.max < 1L << double.mant_dig) {
14 		private alias double F;
15 	} else {
16 		private alias real F;
17 	}
18 
19 	alias T raw_type;
20 	alias F float_type;
21 
22 	T raw;
23 
24 	this(in F v) { this = v; }
25 
26 	static Normalized fromRaw(T v) {
27 		Normalized n;
28 		n.raw = v;
29 		return n;
30 	}
31 
32 	@property F value() const {
33 		return cast(F)raw / T.max;
34 	}
35 
36 	@property ref Normalized value(F v) {
37 		v = min(v, 1);
38 		v = max(v, T.min < 0 ? -1 : 0);
39 		raw = cast(T)(v * T.max);
40 		return this;
41 	}
42 
43 	alias value this;
44 
45 	ref Normalized opOpAssign(string op, T2)(in T2 v) {
46 		mixin("return this = this " ~ op ~ " v;");
47 	}
48 
49 }
50 
51 ///
52 unittest {
53 	Normalized!byte b; // Behaves like a float, but stores -1..1 in a byte as -127..127.
54 	Normalized!uint u; // Stores 0..1 in a uint as 0..uint.max.
55 
56 	// Just use them as if they are floats.
57 	b = 0.5;
58 	u = 0.2;
59 	b *= u;
60 	u = b * u + 0.1;
61 	assert(b < u);
62 	assert(b + 0.05 > u);
63 
64 	// They are automatically capped at 0..1 for unsigned and -1..1 for signed.
65 	b += 3.5;
66 	assert(b == 1);
67 	b = 10 * -0.3;
68 	u = -2.4;
69 	assert(b == -1);
70 	assert(u == 0);
71 
72 	// .raw gives access to the underlying storage.
73 	b = 0.5;
74 	assert(b.raw == 63);
75 	b.raw = -127;
76 	assert(b == -1);
77 	u = 0;
78 	assert(u.raw == 0);
79 	u.raw = 2147483648;
80 	assert(u >= 0.49 && u <= 0.51);
81 
82 	// fromRaw constructs from the raw value.
83 	auto x = Normalized!ubyte(0.4);
84 	auto y = Normalized!ubyte.fromRaw(102); // 102/255
85 	assert(x == y);
86 
87 	// The floating point type it behaves as is the smallest of [float, double, real] with
88 	// at least as much precision as the underlying type.
89 	static assert(is(Normalized!byte.float_type == float));
90 	static assert(is(Normalized!uint.float_type == double));
91 	static assert(is(Normalized!long.float_type == real));
92 
93 	// Normalized!T only contains a T, so a T[] and a Normalized!T[] can be
94 	// casted to eachother.
95 	static assert(Normalized!byte.sizeof == 1);
96 	ubyte[3] rgb_b = [255, 102, 0];
97 	auto rgb_f = cast(Normalized!ubyte[])rgb_b;
98 	// rgb_f is now [1, 0.4, 0]
99 	assert(rgb_f[0] == 1);
100 	rgb_f[1] = 0.6; // Modifies the original byte in rgb_b.
101 	assert(rgb_b[1] == 153);
102 }
103