www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Challenge: Make a data type for holding one of 8 directions allowing

reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
I am in need of a data type for holding direction information; 
one of 8 directions on a single axis. They are named in terms of 
compass directions. If D had a 4-bit datatype, I would just use 
this and do `+=2` whenever I want to change the datatype, but it 
doesn't.

Perhaps this would be a good programming challenge for someone 
more experienced than me. Make a data type (probably a struct) 
for holding one of 8 directional values using 3 bits. It should 
accept the use of increment operators to change the angle.

Ideally (but optionally), it should work as an enum of the same 
name; "Direction".

Here's a unittest that it should ideally pass:
```
unittest
{
     Direction direction = Direction.N;
     direction++;
     assert(direction == Direction.NE);
     direction+=3;
     assert(direction == Direction.S);
     direction--;
     assert(direction == Direction.SE);
     direction-=4;
     assert(direction == Direction.NW);
}
```
Mar 11
next sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
By taking advantage of integer wrapping and a bitwise and, its quite a 
simple problem to solve!

Challenge for the reader: add support for binary operations and toString 
support.

https://dlang.org/spec/operatoroverloading.html

```d
struct Direction {
     private int value;

     Direction opUnary(string op:"++")() {
         value++;
         value &= 7;
         return this;
     }

     Direction opUnary(string op:"--")() {
         value--;
         value &= 7;
         return this;
     }

     void opOpAssign(string op:"+")(int amount) {
         value += amount;
         value &= 7;
     }

     void opOpAssign(string op:"-")(int amount) {
         value -= amount;
         value &= 7;
     }

     enum Direction N = Direction(0);
     enum Direction NE = Direction(1);
     enum Direction E = Direction(2);
     enum Direction SE = Direction(3);
     enum Direction S = Direction(4);
     enum Direction SW = Direction(5);
     enum Direction W = Direction(6);
     enum Direction NW = Direction(7);
}

unittest {
      Direction direction = Direction.N;
      direction++;
      assert(direction == Direction.NE);
      direction+=3;
      assert(direction == Direction.S);
      direction--;
      assert(direction == Direction.SE);
      direction-=4;
      assert(direction == Direction.NW);
}
```
Mar 11
next sibling parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Tuesday, 12 March 2024 at 06:38:28 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 By taking advantage of integer wrapping and a bitwise and, its 
 quite a simple problem to solve!

 Challenge for the reader: add support for binary operations and 
 toString support.

 https://dlang.org/spec/operatoroverloading.html

 ```d
 struct Direction {
     private int value;

     Direction opUnary(string op:"++")() {
         value++;
         value &= 7;
         return this;
     }

     Direction opUnary(string op:"--")() {
         value--;
         value &= 7;
         return this;
     }

     void opOpAssign(string op:"+")(int amount) {
         value += amount;
         value &= 7;
     }

     void opOpAssign(string op:"-")(int amount) {
         value -= amount;
         value &= 7;
     }

     enum Direction N = Direction(0);
     enum Direction NE = Direction(1);
     enum Direction E = Direction(2);
     enum Direction SE = Direction(3);
     enum Direction S = Direction(4);
     enum Direction SW = Direction(5);
     enum Direction W = Direction(6);
     enum Direction NW = Direction(7);
 }

 unittest {
      Direction direction = Direction.N;
      direction++;
      assert(direction == Direction.NE);
      direction+=3;
      assert(direction == Direction.S);
      direction--;
      assert(direction == Direction.SE);
      direction-=4;
      assert(direction == Direction.NW);
 }
 ```
Interesting. I didn't know that an enum can be defined inside a struct like that. I had used functions to get around it. Here is what I had already mostly written, using help from ChatGPT (but only for the opUnary syntax, not the algorithm): ``` struct Direction //One of 8 directions stored in 3 bits { bool[3] d; static Direction N() { return Direction(d:[false,false,false]); } static Direction NE() { return Direction(d:[false,false,true]); } static Direction E() { return Direction(d:[false,true,false]); } static Direction SE() { return Direction(d:[false,true,true]); } static Direction S() { return Direction(d:[true,false,false]); } static Direction SW() { return Direction(d:[true,false,true]); } static Direction W() { return Direction(d:[true,true,false]); } static Direction NW() { return Direction(d:[true,true,true]); } ref Direction opUnary(string op)() if (op == "++" || op == "--") { if (op == "++") const bool up = true; else const bool up = false; if (d[0]) { if (d[1]) d[2] = !d[2]; d[1] = !d[1]; } d[0] = !d[0]; return this; } auto to(T)() const { return cast(T)(d[0] + 2*d[1] + 4*d[2]); } } ``` I am not entirely sure how well it works. I will come back later with an updated version with more functions. I'm not familiar with the syntax of the line `value &= 7;`. Is it equivalent to writing `value = value % 7;`? Anyway, you used an int, but I used an array of 3 bools. I'm guessing that mine uses less memory, but I'm not sure how memory it adds when it's a struct with functions.
Mar 12
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 13/03/2024 11:00 AM, Liam McGillivray wrote:
 I'm not familiar with the syntax of the line |value &= 7;|. Is it 
 equivalent to writing |value = value % 7;|?
& is a bitwise and. LSB 123456789 MSB & 7 LSB 123000000 MSB
 Anyway, you used an int, but I used an array of 3 bools. I'm guessing 
 that mine uses less memory, but I'm not sure how memory it adds when 
 it's a struct with functions.
Due to alignment, it'll probably use just as much. Mine only needs a single ``byte``, at 7 bits it's more than enough. But ``int`` doesn't make much difference unless you are packing instances together ``align(0):`` and realistically cpus are optimized for 32bits not 8.
Mar 12
prev sibling parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Tuesday, 12 March 2024 at 06:38:28 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 By taking advantage of integer wrapping and a bitwise and, its 
 quite a simple problem to solve!

 Challenge for the reader: add support for binary operations and 
 toString support.
Last night I pushed the latest commit to the GitHub repository for my game. It contains the `Direction` struct in [`source/common.d`](https://github.com/LiamM32/Open_Emblem/blob/mast r/source/common.d). Here it is: ``` struct Direction //One of 8 directions stored in 3 bits { import std.conv; import std.traits: isNumeric; bool[3] b; static Direction N = Direction(b:[false,false,false]); static Direction NE = Direction(b:[true,false,false]); static Direction E = Direction(b:[false,true,false]); static Direction SE = Direction(b:[true,true,false]); static Direction S = Direction(b:[false,false,true]); static Direction SW = Direction(b:[true,false,true]); static Direction W = Direction(b:[false,true,true]); static Direction NW = Direction(b:[true,true,true]); ref Direction opUnary(string op)() if (op == "++" || op == "--") { static if (op == "++") const bool up = true; else const bool up = false; if (b[0]) { if (b[1]) b[2] = !b[2]; b[1] = !b[1]; } b[0] = !b[0]; return this; } void opOpAssign(string op)(int amount) if (op == "+" || op == "-") { amount = amount%8; if (amount > 0) for (uint i = 0; i < amount; i++) { static if (op=="+") this++; else this--; } else for (uint i=0; i > amount; i--) { static if (op=="+") this--; else this++; } } T to(T)() const if(isNumeric!T) { return cast(T)(b[0] + 2*b[1] + 4*b[2]); } T opCast(T)() if (isNumeric!T) { return cast(T)(b[0] + 2*b[1] + 4*b[2]); } T to(T)() const if(is(T==string)) { if (this==Direction.N) return "north"; else if (this==Direction.NE) return "northeast"; else if (this==Direction.E) return "east"; else if (this==Direction.SE) return "southeast"; else if (this==Direction.S) return "south"; else if (this==Direction.SW) return "southwest"; else if (this==Direction.W) return "west"; else if (this==Direction.NW) return "northwest"; else throw new Exception("Direction.to!: direction has a value that should be impossible."); //else return ""; //This should never happen. } bool[3] opCast() const { return this.b; } Direction opposite() const { return Direction([b[0], b[1], !b[2]]); } bool diagonal() { return b[0]; } int getAngle() { if (this==Direction.N) return 0; else if (this==Direction.NE) return 45; else if (this==Direction.E) return 90; else if (this==Direction.SE) return 135; else if (this==Direction.S) return 180; else if (this==Direction.SW) return 225; else if (this==Direction.W) return 270; else if (this==Direction.NW) return 315; else throw new Exception("Direction.getAngle: direction has a value that should be impossible."); } } ``` The one thing that cant be done on this is doing `direction+8`. Perhaps it would have been easier if I had chosen to store the value as a ubyte rather than an array of bools. While this is probably over-optimized given the large amount of memory in today's computers, I like it that it can never be an illegitimate value. There's probably some trick I can use to make it easier to figure out the functions to do numerical operations on bits, but I don't know how best to cleanly represent this kind of thing in code. Maybe I need to look for examples of how it's already done. I noticed among the options for overloading unary operations are the symbols "+" & "-". What operation is being overloaded with the function `ref Direction opUnary(string op:"+")(int amount)`?
Mar 13
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
There appears to be a few things that you may not be aware of based upon 
this implementation.

The cost of an add + increment then a bitwise and is only 2-4 cycles on 
a Haswell cpu. Depending upon if its working solely in registers (via 
inlining) or its operating on ram.

The cost of a move from ram into a register is about 1 cycle, but can 
have latencies around 3 cycles if not cached.

Whereas if you need to do branching (if statement, loops), this is an 
unpredictable cost and loops where a simple bitwise operation can be 
done is out right non-optimized.

As for methods like to:

```d
static immutable string[] table = ["north", ...];
return table[this.value];
```

That's two moves from ram to register.

In essence in your design, you have blown out the cost by a significant 
margin well beyond the 12% that is described as being the minimum to 
consider optimization.

If you want to understand where the 12% number comes from I suggest 
reading the paper "Structured Programming with go to Statements" by 
Donald Knuth.

As for exceptions, totally not required. You can solve this by simply 
making your state private so nobody else can mutate it. Bounds checking 
will ensure if the state is corrupted it'll error out if you use the 
lookup method I suggested above.

Understanding what I have said is very important for game engine 
programmers. So if you're interested in it beyond some hobbyist 
endeavors you have some reading up to do ;)
Mar 13
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Thursday, 14 March 2024 at 01:58:46 UTC, Richard (Rikki) 
Andrew Cattermole wrote:
 The cost of an add + increment then a bitwise and is only 2-4 
 cycles on a Haswell cpu. Depending upon if its working solely 
 in registers (via inlining) or its operating on ram.

 Whereas if you need to do branching (if statement, loops), this 
 is an unpredictable cost and loops where a simple bitwise 
 operation can be done is out right non-optimized.

 As for exceptions, totally not required. You can solve this by 
 simply making your state private so nobody else can mutate it. 
 Bounds checking will ensure if the state is corrupted it'll 
 error out if you use the lookup method I suggested above.
I tried to rework the functions to use bitwise operations, but it was difficult to figure out the correct logic. I decided that it's not worth the hassle, so I just changed the value storage from `bool[3]` to `ubyte`. Now it works much more like your version. https://github.com/LiamM32/Open_Emblem/blob/c2014ab3f77e89c0cedcd6dbf7f8362ebfac33a9/source/common.d I did a little reading, so now I understand what it means when you have `&= 7`. But I want to ask, is this faster than `%= 8`? If not, I would like to change it to the latter for readability.
Mar 14
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 14 March 2024 at 23:39:33 UTC, Liam McGillivray 
wrote:
 On Thursday, 14 March 2024 at 01:58:46 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 [...]
I tried to rework the functions to use bitwise operations, but it was difficult to figure out the correct logic. I decided that it's not worth the hassle, so I just changed the value storage from `bool[3]` to `ubyte`. Now it works much more like your version. https://github.com/LiamM32/Open_Emblem/blob/c2014ab3f77e89c0cedcd6dbf7f8362ebfac33a9/source/common.d I did a little reading, so now I understand what it means when you have `&= 7`. But I want to ask, is this faster than `%= 8`? If not, I would like to change it to the latter for readability.
`%=8` will be codegened using slower intructions w/o optimz enabled but with `&=7` you directly get the right instruction, which does not involves integer division. See https://godbolt.org/z/74vbba5aG
Mar 14
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 15/03/2024 12:47 PM, Basile B. wrote:
 On Thursday, 14 March 2024 at 23:39:33 UTC, Liam McGillivray wrote:
 On Thursday, 14 March 2024 at 01:58:46 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 [...]
I tried to rework the functions to use bitwise operations, but it was difficult to figure out the correct logic. I decided that it's not worth the hassle, so I just changed the value storage from `bool[3]` to `ubyte`. Now it works much more like your version. https://github.com/LiamM32/Open_Emblem/blob/c2014ab3f77e89c0cedcd6dbf7f8362ebfac33a9/source/common.d I did a little reading, so now I understand what it means when you have `&= 7`. But I want to ask, is this faster than `%= 8`? If not, I would like to change it to the latter for readability.
`%=8` will be codegened using slower intructions w/o optimz enabled but with `&=7` you directly get the right instruction, which does not involves integer division. See https://godbolt.org/z/74vbba5aG
Yes, it'll depend upon how smart the compiler is at optimizing and it may not occur in non-optimizing builds. The modulas instructions are based upon division, this is an incredibly expensive operation. https://stackoverflow.com/a/8022107 The division instruction on Haswell for integers ranges from 9 cycles for 8bit, all the way up to 36 cycles for 64bit.
Mar 14
parent Basile B. <b2.temp gmx.com> writes:
On Friday, 15 March 2024 at 00:00:01 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 15/03/2024 12:47 PM, Basile B. wrote:
 On Thursday, 14 March 2024 at 23:39:33 UTC, Liam McGillivray 
 wrote:
 On Thursday, 14 March 2024 at 01:58:46 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 [...]
I tried to rework the functions to use bitwise operations, but it was difficult to figure out the correct logic. I decided that it's not worth the hassle, so I just changed the value storage from `bool[3]` to `ubyte`. Now it works much more like your version. https://github.com/LiamM32/Open_Emblem/blob/c2014ab3f77e89c0cedcd6dbf7f8362ebfac33a9/source/common.d I did a little reading, so now I understand what it means when you have `&= 7`. But I want to ask, is this faster than `%= 8`? If not, I would like to change it to the latter for readability.
`%=8` will be codegened using slower intructions w/o optimz enabled but with `&=7` you directly get the right instruction, which does not involves integer division. See https://godbolt.org/z/74vbba5aG
Yes, it'll depend upon how smart the compiler is at optimizing and it may not occur in non-optimizing builds.
Indeed GDC (so very likely GCC too, or whatever language uses it as backend...) does it without optimz (https://godbolt.org/z/Ke7c54Gqj). That's not very surprising. If you look at LLVM bug tracker, for the tag "missed optimisations", in the report body you'll see a lot of "GDC does that but we dont", even if here it's a bit different, as optimz are not enabled.
Mar 14
prev sibling parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Mar 14, 2024 at 11:39:33PM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
[...]
 I tried to rework the functions to use bitwise operations, but it was
 difficult to figure out the correct logic. I decided that it's not
 worth the hassle, so I just changed the value storage from `bool[3]`
 to `ubyte`.
[...] Just wanted to note that while in theory bool[3] could be optimized by the compiler for compact storage, what you're most likely to get is 3 bytes, one for each bool, or perhaps even 3 ints (24 bytes). When dealing with units of data smaller than a byte, you generally need to do it manually, because memory is not addressable by individual bits, making it difficult to implement things like slicing an array of bool. So the compiler is most likely to simplify things by making it an array of bytes rather than emit complex bit manipulation code to make up for the lack of bit-addressability in the underlying hardware. Using bit operators like others have pointed out in this thread is probably the best way to implement what you want. T -- LINUX = Lousy Interface for Nefarious Unix Xenophobes.
Mar 14
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Friday, 15 March 2024 at 00:21:42 UTC, H. S. Teoh wrote:
 On Thu, Mar 14, 2024 at 11:39:33PM +0000, Liam McGillivray via 
 Digitalmars-d-learn wrote: [...]
 I tried to rework the functions to use bitwise operations, but 
 it was difficult to figure out the correct logic. I decided 
 that it's not worth the hassle, so I just changed the value 
 storage from `bool[3]` to `ubyte`.
[...] Just wanted to note that while in theory bool[3] could be optimized by the compiler for compact storage, what you're most likely to get is 3 bytes, one for each bool, or perhaps even 3 ints (24 bytes). When dealing with units of data smaller than a byte, you generally need to do it manually, because memory is not addressable by individual bits, making it difficult to implement things like slicing an array of bool. So the compiler is most likely to simplify things by making it an array of bytes rather than emit complex bit manipulation code to make up for the lack of bit-addressability in the underlying hardware. Using bit operators like others have pointed out in this thread is probably the best way to implement what you want. T
I'm curious as to what "manual implementation" would mean, since clearly making my own struct with `bool[3]` doesn't count. Does D have features for precise memory manipulation? Anyway, I'm surprised that D has a special operator `&=` for doing bit manipulation on integers, especially given that the steps to convert an int into a bool array is more complicated. I would imagine the former would be a rather niche thing.
Mar 16
parent "H. S. Teoh" <hsteoh qfbox.info> writes:
On Sat, Mar 16, 2024 at 09:16:51PM +0000, Liam McGillivray via
Digitalmars-d-learn wrote:
 On Friday, 15 March 2024 at 00:21:42 UTC, H. S. Teoh wrote:
[...]
 When dealing with units of data smaller than a byte, you generally
 need to do it manually, because memory is not addressable by
 individual bits, making it difficult to implement things like
 slicing an array of bool.
[...]
 I'm curious as to what "manual implementation" would mean, since
 clearly making my own struct with `bool[3]` doesn't count. Does D have
 features for precise memory manipulation?
Manual implementation as in you would deal with the machine representation in terms of bytes, or more likely, uints (on modern CPUs even though bytes are individually addressible, the hardware actually works in terms of a larger unit, typically an 4-byte 32-bit unit, or an 8-byte 64-bit unit), using bitwise operators to manipulate the bits the way you want to.
 Anyway, I'm surprised that D has a special operator `&=` for doing bit
 manipulation on integers, especially given that the steps to convert
 an int into a bool array is more complicated. I would imagine the
 former would be a rather niche thing.
You should understand that bitwise operators are directly implemented in hardware, and thus operators like &, |, ^, <<, >>, ~, etc., typically map directly to individual CPU instructions. As such, they are very fast, and preferred when you're doing bit-level manipulations. At this level, you typically do not work with individual bits per se, but with machine words (typically 32-bit or 64-bit units). Bitwise operators operate on all 32 or 64 bits at once, so performance-aware code typically manipulates all these bits simultaneously rather than individually. Of course, using suitable bit-masking you *can* address individual bits, but the hardware instructions themselves typically work with all 32/64 bits at once. Here's a simple example. Suppose you have 3 bits you want to store. Since the machine doesn't have a 3-bit built-in type, you typically just use the next larger available size, either a ubyte (8 bits) if you want compact storage, or if compactness isn't a big issue just a uint (32 bits, you just ignore the other 29 bits that you don't need). So you'd declare the storage something like this: uint myBits; Bits are usually indexed from 0, so bit 0 is the first position, bit 1 is the second position, and so on. So to set the first bit to 1, you'd do: myBits |= 0b001; Note that at the machine level, this operator works on all 32 bits at the same time. Most of the bits remain unchanged, though, because bitwise OR does not change the original value if the operand is 0. So the overall effect is that the first bit is set. To set the first bit to 0, there isn't a direct operator that does that, but you can take advantage of the behaviour of bitwise AND, in which any bit which is 0 in the operand will get cleared, everything else remains unchanged. So you'd do this: myBits &= 0b110; Now, since we don't really care about the other 29 bits, we could write this as follows instead, to make our intent clearer: myBits &= ~0b001; The ~ operator flips all the bits, so this is equivalent to writing: myBits &= ~0b1111_1111_1111_1111_1111_1111_1111_1110; Writing it with ~ also has the advantage that should we later decide to add another bit to our "bit array", we don't have to update the code; whereas if we'd used `myBits &= 0b110;` then we'd need to change it to `myBits &= 0b1110;` otherwise our new 4th bit may get unexpectedly cleared when we only wanted to clear the first bit. Now, what if we wanted to set both the 1st and 3rd bits? In a hypothetical bit array implementation, we'd do the equivalent of: bool[3] myBits; myBits[0] = 1; myBits[2] = 1; However, in our uint approach, we can cut the number of operations by half, because the CPU is already operating on the entire 32 bits of the uint at once -- so there's no need to have two instructions to set two individual bits when we could just do it all in one: myBits |= 0b101; // look, ma! both bits set at once! Similarly, to clear the 1st and 3rd bits simultaneously, we simply write: myBits &= ~0b101; // clear both bits in 1 instruction! Of course, when we only have 3 bits to work with, the savings isn't that significant. However, if you have a larger bit array, say you need an array of 32 bits, this can speed your code up by 32x, because you're taking advantage of the fact that the hardware is already operating on all 32 bits at the same time. On 64-bit CPUs, you can speed it up by 64x because the CPU operates on all 64 bits simultaneously, so you can manipulate an entire array of 64 bits in a single instruction, which is 64x faster than if you looped over an array of bool with 64 iterations. T -- Without outlines, life would be pointless.
Mar 16
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
On Tuesday, 12 March 2024 at 05:38:03 UTC, Liam McGillivray wrote:
 Perhaps this would be a good programming challenge for someone 
 more experienced than me. Make a data type (probably a struct) 
 for holding one of 8 directional values using 3 bits. It should 
 accept the use of increment operators to change the angle.
D is such a perfect language that you can do the features you mentioned and more. I also implemented the following during my rookie years: ```d alias Direction = enumSet; union enumSet(T) { T e; alias e this; struct { int i; auto opUnary(string op: "++")() => i = i < e.max ? ++i : e.min; auto opUnary(string op: "--")() => i = i > e.min ? --i : e.max; auto opOpAssign(string op: "+")(int val) { i += val; if(i > e.max) i -= e.max + 1; } auto opOpAssign(string op: "-")(int val) { i -= val; if(i < e.min) i += e.max + 1; } } string toString() const => e.to!string; } unittest { auto direction = Direction!Directions(Directions.N); direction++; assert(direction == Directions.NE); direction+=3; assert(direction == Directions.S); direction--; assert(direction == Directions.SE); direction-=4; assert(direction == Directions.NW); } import std.stdio, std.conv; void main() { enum Directions { N , NE , E, SE, S, SW , W, NW } auto test = enumSet!Directions(Directions.W); test += 9; /* ++test; ++test; ++test; ++test; ++test; ++test; ++test; ++test; ++test;//*/ test.writeln; test--; test--; test.writeln; } ``` Here union was used with an extra template parameter. I also added 2 aliases to avoid confusion. SDB 79
Mar 12
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Tuesday, 12 March 2024 at 05:38:03 UTC, Liam McGillivray wrote:
 I am in need of a data type for holding direction information; 
 one of 8 directions on a single axis. They are named in terms 
 of compass directions. If D had a 4-bit datatype, I would just 
 use this and do `+=2` whenever I want to change the datatype, 
 but it doesn't.

 Perhaps this would be a good programming challenge for someone 
 more experienced than me. Make a data type (probably a struct) 
 for holding one of 8 directional values using 3 bits. It should 
 accept the use of increment operators to change the angle.

 Ideally (but optionally), it should work as an enum of the same 
 name; "Direction".

 Here's a unittest that it should ideally pass:
 ```
 unittest
 {
     Direction direction = Direction.N;
     direction++;
     assert(direction == Direction.NE);
     direction+=3;
     assert(direction == Direction.S);
     direction--;
     assert(direction == Direction.SE);
     direction-=4;
     assert(direction == Direction.NW);
 }
 ```
While there are workarounds (as proposed in the other answers, using operator overloads) I tend to think that the way currently enums work with arithmetic operators is a symptom of an incomplete type-system. Enums made of integer numbers are sub classes of the parent [integral sequence](https://en.wikipedia.org/wiki/Integer_sequence). The semantics of the operators are actually not as clear as that. What if you define ```d enum Direction { N = 1, NE, S = 45, SW } ``` ? You directly see that the proposed workarounds dont work anymore. There are natural numbers, sub-sets of natural numbers, unordered sequences, etc. The type system does not represent that. Anyway, I dont blame D, plenty of languages have the exact same problem.
Mar 13
parent Salih Dincer <salihdb hotmail.com> writes:
On Wednesday, 13 March 2024 at 10:27:49 UTC, Basile B. wrote:
 The semantics of the operators are actually not as clear as 
 that. What if you define

 ```d
 enum Direction
 {
     N = 1, NE, S = 45, SW
 }
 ```

 ?
Certainly! EnumMembers; can be used. The EnumMembers template from the std.traits module is used to retrieve all the members of an enumeration. It generates a tuple containing all the enumeration values, which can be iterated over using a foreach loop. In the D programming language, you can use EnumMembers to iterate over enum values at compile time, which is useful for generating code based on enum members. Here’s a simple code example: ```d enum Days { Monday = 1001, Tuesday = 1010, Wednesday = 1011, Thursday = 1100, Friday = 1101, Saturday = 1110, Sunday = 1111 } import std.traits : EnumMembers; foreach(day; EnumMembers!Days) day.writeln(":", cast(int)day); ``` SDB 79
Mar 13
prev sibling parent reply Daniel N <no public.email> writes:
On Tuesday, 12 March 2024 at 05:38:03 UTC, Liam McGillivray wrote:
 I am in need of a data type for holding direction information; 
 one of 8 directions on a single axis. They are named in terms 
 of compass directions. If D had a 4-bit datatype, I would just 
 use this and do `+=2` whenever I want to change the datatype, 
 but it doesn't.

 Perhaps this would be a good programming challenge for someone 
 more experienced than me. Make a data type (probably a struct) 
 for holding one of 8 directional values using 3 bits. It should 
 accept the use of increment operators to change the angle.

 Ideally (but optionally), it should work as an enum of the same 
 name; "Direction".

 Here's a unittest that it should ideally pass:
D actually supports both 3 and 4 bit integers. People will likely warn you of minor portability risks... but if you target a limited amount of platforms and prefer concise readable code, then it's a text book example for bitfields. The risk can easily be mitigated by having an unittest to catch the error directly(if you try to port to some exotic platform). dmd -preview=bitfields (Some lines stolen from Rikki) ```d struct Direction { private uint value : 3; alias this = value; enum Direction N = Direction(0); enum Direction NE = Direction(1); enum Direction E = Direction(2); enum Direction SE = Direction(3); enum Direction S = Direction(4); enum Direction SW = Direction(5); enum Direction W = Direction(6); enum Direction NW = Direction(7); } ```
Mar 15
parent reply Liam McGillivray <yoshi.pit.link.mario gmail.com> writes:
On Friday, 15 March 2024 at 17:25:09 UTC, Daniel N wrote:
 On Tuesday, 12 March 2024 at 05:38:03 UTC, Liam McGillivray 
 wrote:
 I am in need of a data type for holding direction information; 
 one of 8 directions on a single axis. They are named in terms 
 of compass directions. If D had a 4-bit datatype, I would just 
 use this and do `+=2` whenever I want to change the datatype, 
 but it doesn't.

 Perhaps this would be a good programming challenge for someone 
 more experienced than me. Make a data type (probably a struct) 
 for holding one of 8 directional values using 3 bits. It 
 should accept the use of increment operators to change the 
 angle.

 Ideally (but optionally), it should work as an enum of the 
 same name; "Direction".

 Here's a unittest that it should ideally pass:
D actually supports both 3 and 4 bit integers. People will likely warn you of minor portability risks... but if you target a limited amount of platforms and prefer concise readable code, then it's a text book example for bitfields. The risk can easily be mitigated by having an unittest to catch the error directly(if you try to port to some exotic platform). dmd -preview=bitfields (Some lines stolen from Rikki) ```d struct Direction { private uint value : 3; alias this = value; enum Direction N = Direction(0); enum Direction NE = Direction(1); enum Direction E = Direction(2); enum Direction SE = Direction(3); enum Direction S = Direction(4); enum Direction SW = Direction(5); enum Direction W = Direction(6); enum Direction NW = Direction(7); } ```
Oh wow! That looks so clean and elegant, aside from the `: 3` part being easy to miss, and not very readable for those not aware of this feature. This would be so simple. If I used this, I wouldn't even need to make a struct and write the operator overload functions; just make an enum for a 3-bit uint. Based on the words in the command and a quick search, I'm guessing that this is an experimental feature that has not yet been accepted as part of the language. Perhaps I shouldn't use this then, just in case it gets pulled and someone who discovers my project in the future will have a build error that they don't know how to solve. This seems like a bigger problem than the extra memory the current ubyte version takes up, which is probably quite small for a computer of today anyway. I suppose this would be a nice feature for the language to have if the syntax were reworked. Perhaps something like `uint!3 value;` would be better.
Mar 16
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
Bit fields are currently going through the DIP process, although because 
of ImportC having it, its just a matter of turning them on and adding 
the parser stuff.

However there is a major drawback to it and is why you'll still need to 
use a struct and that is you can't take a pointer to it.
Mar 16