## 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.
bearophile <bearophileHUGS lycos.com> writes:
```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

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
Timon Gehr <timon.gehr gmx.ch> writes:
``` 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

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

That 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
Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
```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):

I'm not sure what is making the code faster, but on my machine I get:
classifyNumberNew: 5543
classifyNumberOld: 5550
```
May 13 2011
Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
```Wow sorry please neglect that, I completely wiped out an entire line
of code. Ignore that code please.
```
May 13 2011
Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
```Or wait, I didn't? I think I'm confusing two implementations now. LOL.
```
May 13 2011