digitalmars.D - enum conversions
- bearophile (47/47) May 13 2011 Some musings, feel free to ignore this post.
- Timon Gehr (28/77) May 13 2011 do it efficiently (this means with minimal or no runtime overhead), and ...
- Andrej Mitrovic (8/8) May 13 2011 Well I don't know about safety, but this will throw an exception at
- Andrej Mitrovic (2/2) May 13 2011 Wow sorry please neglect that, I completely wiped out an entire line
- Andrej Mitrovic (1/1) May 13 2011 Or wait, I didn't? I think I'm confusing two implementations now. LOL.
Some musings, feel free to ignore this post. Sometimes I have to convert enums to integers or integers to enums. I'd like to do it efficiently (this means with minimal or no runtime overhead), and safely (this means I'd like the type system to prove I am not introducing bugs, like assigning enums that don't exist). This function classifies every natural number in one of the three classes (deficient numbers, perfect numbers, and abundant nubers, according to the sum of its factors), so I use a 3-enum: enum NumberClass : int { deficient=-1, perfect=0, abundant=1 } NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int difference = reduce!q{a + b}(0, factors) - n; return cast(NumberClass)sgn(difference); } std.math.sgn() returns a value in {-1, 0, 1}, so this first version of the function uses just a cast, after carefully defining the same values for the NumberClass enums. But casts stop the type system, so it can't guaranteed the code is working correctly or safely, so if I change the values of the enums the type system doesn't catch the bug. This version is safer, works with any value associated to the enum items, but it performs even two tests at run-time: NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int diff = sgn(reduce!q{a + b}(0, factors) - n); if (diff == -1) return NumberClass.deficient; else if (diff == 0) return NumberClass.perfect; else return NumberClass.abundant; } This version is about as safe, and uses one array access on immutable array (I have not used an emum array to avoid wasting even more run time): NumberClass classifyNumber(int n) static immutable res = [NumberClass.deficient, NumberClass.perfect, NumberClass.abundant]; auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int sign = sgn(reduce!q{a + b}(0, factors) - n); return res[sign + 1]; } Using a switch is another safe option, I can't use a final switch. This too has some run-time overhead: NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int sign = sgn(reduce!q{a + b}(0, factors) - n); switch (sign) { case -1: return NumberClass.deficient; case 0: return NumberClass.perfect; default: return NumberClass.abundant; } } In theory a bit better type system (with ranged integers too as first-class types) knows that sgn() returns the same values as the enum NumberClass, this allows the first version without cast and compile-time proof of correctness: NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int difference = reduce!q{a + b}(0, factors) - n; return sgn(difference); } I don't know what to think. Bye, bearophile
May 13 2011
Some musings, feel free to ignore this post. Sometimes I have to convert enums to integers or integers to enums. I'd like todo it efficiently (this means with minimal or no runtime overhead), and safely (this means I'd > like the type system to prove I am not introducing bugs, like assigning enums that don't exist).This function classifies every natural number in one of the three classes(deficient numbers, perfect numbers, and abundant nubers, according to the sum of its factors), so I use a 3-enum:enum NumberClass : int { deficient=-1, perfect=0, abundant=1 } NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int difference = reduce!q{a + b}(0, factors) - n; return cast(NumberClass)sgn(difference); } std.math.sgn() returns a value in {-1, 0, 1}, so this first version of thefunction uses just a cast, after carefully defining the same values for the NumberClass enums. Butcasts stop the type system, so it can't guaranteed the code is working correctlyor safely, so if I change the values of the enums the type system doesn't catch the bug.This version is safer, works with any value associated to the enum items, but itperforms even two tests at run-time:NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int diff = sgn(reduce!q{a + b}(0, factors) - n); if (diff == -1) return NumberClass.deficient; else if (diff == 0) return NumberClass.perfect; else return NumberClass.abundant; } This version is about as safe, and uses one array access on immutable array (Ihave not used an emum array to avoid wasting even more run time):NumberClass classifyNumber(int n) static immutable res = [NumberClass.deficient, NumberClass.perfect,NumberClass.abundant];auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int sign = sgn(reduce!q{a + b}(0, factors) - n); return res[sign + 1]; } Using a switch is another safe option, I can't use a final switch. This too hassome run-time overhead:NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int sign = sgn(reduce!q{a + b}(0, factors) - n); switch (sign) { case -1: return NumberClass.deficient; case 0: return NumberClass.perfect; default: return NumberClass.abundant; } } In theory a bit better type system (with ranged integers too as first-classtypes) knows that sgn() returns the same values as the enum NumberClass, this allows the firstversion without cast and compile-time proof of correctness: NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int difference = reduce!q{a + b}(0, factors) - n; return sgn(difference); } I don't know what to think. Bye, bearophileThat overhead you are referring to gets negligible even for moderately large n. An entirely safe version of the code without any overhead would be: enum NumberClass : int { deficient=-1, perfect=0, abundant=1 } NumberClass classifyNumber(int n) auto factors = filter!((i){ return n % i == 0; })(iota(1, n)); int difference = reduce!q{a + b}(0, factors) - n; //guard the cast: static assert(NumberClass.min==-1 && NumberClass.max==1); static assert(cast(int)NumberClass.deficient==-1 && cast(int)NumberClass.perfect==0 && cast(int)NumberClass.abundant==1); return cast(NumberClass)sgn(difference); } This is not the way of least resistance though.
May 13 2011
Well I don't know about safety, but this will throw an exception at runtime on invalid enum values, and it also seems to perform a little better than your original classifyNumber function (The classifyNumberNewImpl function is the new one): http://codepad.org/qyUaD1Hj I'm not sure what is making the code faster, but on my machine I get: classifyNumberNew: 5543 classifyNumberOld: 5550
May 13 2011
Wow sorry please neglect that, I completely wiped out an entire line of code. Ignore that code please.
May 13 2011
Or wait, I didn't? I think I'm confusing two implementations now. LOL.
May 13 2011