Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-16 Thread Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn
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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-16 Thread H. S. Teoh via Digitalmars-d-learn
On Sat, Mar 16, 2024 at 09:16:51PM +, 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 &= ~0b_______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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-16 Thread Liam McGillivray via Digitalmars-d-learn

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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-16 Thread Liam McGillivray via Digitalmars-d-learn

On Friday, 15 March 2024 at 00:21:42 UTC, H. S. Teoh wrote:
On Thu, Mar 14, 2024 at 11:39:33PM +, 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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-15 Thread Daniel N via Digitalmars-d-learn

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);
}
```


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-14 Thread H. S. Teoh via Digitalmars-d-learn
On Thu, Mar 14, 2024 at 11:39:33PM +, 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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-14 Thread Basile B. via Digitalmars-d-learn
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.





Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-14 Thread Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn



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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-14 Thread Basile B. via Digitalmars-d-learn
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


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-14 Thread Liam McGillivray via Digitalmars-d-learn
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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-13 Thread Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn
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 ;)


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-13 Thread Liam McGillivray via Digitalmars-d-learn
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/master/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)`?


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-13 Thread Salih Dincer via Digitalmars-d-learn

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= 
 }

 import std.traits : EnumMembers;
 foreach(day; EnumMembers!Days)
   day.writeln(":", cast(int)day);
```

SDB@79


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-13 Thread Basile B. via Digitalmars-d-learn

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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-12 Thread Salih Dincer via Digitalmars-d-learn

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


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-12 Thread Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn

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 12300 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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-12 Thread Liam McGillivray via Digitalmars-d-learn
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.


Re: Challenge: Make a data type for holding one of 8 directions allowing increment and overflow

2024-03-12 Thread Richard (Rikki) Andrew Cattermole via Digitalmars-d-learn
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);
}
```