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