Jump to page: 1 2
Thread overview
legacy code retreat's triva game : the D version
Dec 20, 2013
marcpmichel
Dec 20, 2013
bearophile
Dec 20, 2013
marcpmichel
Dec 20, 2013
bearophile
Dec 21, 2013
Chris Cain
Dec 21, 2013
Ivan Kazmenko
Dec 21, 2013
marcpmichel
Dec 21, 2013
bearophile
Dec 21, 2013
Meta
Dec 21, 2013
bearophile
Dec 22, 2013
ilya-stromberg
Dec 22, 2013
John Colvin
Dec 22, 2013
Marco Leise
Dec 22, 2013
Timon Gehr
Dec 22, 2013
Timon Gehr
Dec 22, 2013
Marco Leise
Dec 22, 2013
Chris Cain
Dec 22, 2013
Timon Gehr
Dec 22, 2013
Marco Leise
December 20, 2013
I participated in the "global day of code retreat 2013", and we had to do refactoring on a very ugly piece of code which was available on many languages.
But there was no D version, so I made one (based on the java version) and pull-requested it.

Here is the ugly thing :
https://github.com/jbrains/trivia/tree/master/d

EOT
December 20, 2013
marcpmichel:

> Here is the ugly thing :
> https://github.com/jbrains/trivia/tree/master/d

And wrong:

> if (rand.front() % 9 == 7) {

Bye,
bearophile
December 20, 2013
On Friday, 20 December 2013 at 15:05:07 UTC, bearophile wrote:
> marcpmichel:
>
>> Here is the ugly thing :
>> https://github.com/jbrains/trivia/tree/master/d
>
> And wrong:
>
>> if (rand.front() % 9 == 7) {
>
> Bye,
> bearophile

Do you mean I should have used :
if (uniform(0,10) == 7) {
instead ?
December 20, 2013
marcpmichel:

> Do you mean I should have used :
> if (uniform(0,10) == 7) {
> instead ?

Right. Using % introduces a bias.

Bye,
bearophile
December 21, 2013
On Friday, 20 December 2013 at 16:20:44 UTC, marcpmichel wrote:
> On Friday, 20 December 2013 at 15:05:07 UTC, bearophile wrote:
>> marcpmichel:
>>
>>> Here is the ugly thing :
>>> https://github.com/jbrains/trivia/tree/master/d
>>
>> And wrong:
>>
>>> if (rand.front() % 9 == 7) {
>>
>> Bye,
>> bearophile
>
> Do you mean I should have used :
> if (uniform(0,10) == 7) {
> instead ?

TL;DR version:
Actually, the equivalent would be uniform(0, 9), but yes, that'd be the preferable approach there. (also note https://github.com/jbrains/trivia/blob/7b473f9fbbd125b0ab1c2e82582b8a8c414ca501/d/source/trivia.d#L19 too should be changed to `uniform(1, 6)` which will give numbers in the range [1 .. 6) ... that's what you want, right?)

Long version:
For more information, I've written a document on an implementation of uniform (which should be coming in 2.065, btw) which discusses the issue with just using the modulus operator:
https://dl.dropboxusercontent.com/u/2206555/uniformUpgrade.pdf

Generally speaking, this new uniform will be _extremely_ close to the same speed of just using the modulus operator, but avoids the bias issue. I think there is no real good reason to not use the standard function anymore.

That said, the bias with such a small number (9) won't be significant. If rand gives you a uniform 32-bit number, then the distribution of rand % 9 will be [477218589, 477218589, 477218589, 477218589, 477218588, 477218588, 477218588, 477218588, 477218588] (notice how the first 4 have 1 more occurrence than the rest?)... so the bias is miniscule in this case.

The bias issue matters a lot more with larger numbers where some numbers could actually occur twice as often as others, or if your application demands high quality random numbers (think gambling games). Related to those reasons, even if your code doesn't use large numbers and isn't used for a gambling game now, it's still possible for it to eventually be used for such things (or to influence others to follow your example for the bad situations). For those reasons alone, it's pretty important to get in the habit of using the standard function.

But that's not all since the standard function is probably easier to read, too. Let's say you want to emulate a standard 6-sided die. If you want numbers in the range [1..6] (note inclusive bounds) it's easier to see immediately when you say `uniform!"[]"(1, 6)' rather than `rand % 6 + 1`

That's probably all TMI, but maybe all of that will be useful for you.
December 21, 2013
On Saturday, 21 December 2013 at 05:12:57 UTC, Chris Cain wrote:
> For more information, I've written a document on an implementation of uniform (which should be coming in 2.065, btw) which discusses the issue with just using the modulus operator:
> https://dl.dropboxusercontent.com/u/2206555/uniformUpgrade.pdf

Looks like your new implementation has one modulo operator, compared to the previous one having two divisions.  That may be the cause of speedup.

The previous implementation was, by its looks, copied from C++ Boost which also uses two divisions.  Do you know the reason for that?  They seem to have been solving the exact same problem (strict uniformness provided that the underlying RNG is uniform).

I'd like to touch a relevant point here that matters for me.  In a mature randomness library, one important quality is reproducibility: there are applications where you want to use pseudo-random values, but generate the exact same pseudo-random values across different versions, computers, operating systems and language implementations.  So far I have seen very few languages which provide such reproducibility guarantees for their standard library.  For example, in C and C++ standard randomness library, the details were implementation-dependent all the way until the recent C++11.  Python stood for long but finally broke it between 3.1 and 3.2 because of the exact same non-uniformness problem.  A positive example in this regard is Java which enforces the implementation of Random since at least version 1.5.

If you break the reproducibility of uniform in dmd 2.065, there should be at least a note on that in its documentation.  For a mature library, I think the old implementation should also have been made available somehow. (well, there's always an option to include an old library version in your project, but...)  Perhaps that's not the case for D and Phobos since they are still not stabilized.  Especially so for std.random which is due to more breakage anyway because of the value/reference issues with RNG types.

Regarding that, I have a point on designing a randomness library.  Right now, most of what I have seen has at most two layers: the core RNG providing random bits, and the various uses of these bits, like uniform distribution on a segment, random shuffle and so on.  It is comfortable when the elements of the two layers are independent, and you can compose different first layers (LCG, MT19937, or maybe some interface to /dev/*random) with different second layer functions (uniform[0,9], random_shuffle, etc.).  Still, many of the useful second level functions build upon uniform distribution for integers on a segment.  Thus I would like to have an explicit intermediate layer consisting of uniform and maybe other distributions which could also have different (fast vs. exact) implementations to choose from.  In the long run, such design could also solve reproducibility problems: we can provide another implementation of uniform as the default, but it is still easy to set the previous one as the preferred intermediate level.

Ivan Kazmenko.
December 21, 2013
>> Do you mean I should have used :
>> if (uniform(0,10) == 7) {
>> instead ?
>
> TL;DR version:
> Actually, the equivalent would be uniform(0, 9), but yes, that'd be the preferable approach there. (also note https://github.com/jbrains/trivia/blob/7b473f9fbbd125b0ab1c2e82582b8a8c414ca501/d/source/trivia.d#L19 too should be changed to `uniform(1, 6)` which will give numbers in the range [1 .. 6) ... that's what you want, right?)

Indeed, your're right, thanks.

I used the modulo trick for multiple reasons :
* I ported the java source, which used the basic java.util.random's Random.nextInt() then a modulo to cap the output.
* D's std.random had me scratching my head for minutes; like : "What is this mess ? And where is the simple rand() function ?"
* I didn't care about speed or uniformness of the generated numbers.
* While in the code retreat event, we tried to get a "golden master" ( the output of the program ), to be able to test that refactoring didn't change anything. One trick is to set the seed of the random number generator to guarantee we always got the same dice rolls. And the std.random complexity didn't help to choose the right method.

That being said, there are worse things in game.d : I introduced new bugs in this already buggy program, by using D's array slices.
https://github.com/jbrains/trivia/blob/7b473f9fbbd125b0ab1c2e82582b8a8c414ca501/d/source/game.d#L101

Lastly, this tiny contribution is just a drop in the ocean of "spreading the world about D".
December 21, 2013
Chris Cain:

> https://dl.dropboxusercontent.com/u/2206555/uniformUpgrade.pdf

From page 6:

size_t[] counts = new size_t[](top);
foreach(i; 0 .. 500_000_000)
    counts[uniform(0, top)] += 1;


Modern D allows you to write better code:

size_t[N] counts;
foreach (immutable _; 0 .. 500_000_000)
    counts[uniform(0, $)]++;

Bye,
bearophile
December 21, 2013
On Saturday, 21 December 2013 at 15:03:34 UTC, bearophile wrote:
> Chris Cain:
>
>> https://dl.dropboxusercontent.com/u/2206555/uniformUpgrade.pdf
>
> From page 6:
>
> size_t[] counts = new size_t[](top);
> foreach(i; 0 .. 500_000_000)
>     counts[uniform(0, top)] += 1;
>
>
> Modern D allows you to write better code:
>
> size_t[N] counts;
> foreach (immutable _; 0 .. 500_000_000)
>     counts[uniform(0, $)]++;
>
> Bye,
> bearophile

I know immutable is a good thing, but don't you think `immutable _` is a bit unnecessary in this case?
December 21, 2013
Meta:

> I know immutable is a good thing, but don't you think `immutable _` is a bit unnecessary in this case?

Some answer, choose the one you prefer:

1) Yes, it's totally useless because the _ variable is not even used inside the loop body! So sorry, I'm always so pedantic.

2) It's necessary, don't you see that? You don't need to mutate that variable, so it's better for it be immutable. A simple rule to follow is to make const/immutable all variables that don't need to mutate, to make code simpler and safer. There's no real reason to break that general rule in this case.

3) Just like the integer '5' a range of values as 0 .. 1000 is an immutable value. So a variable that scans such range should be immutable. If you really want to mutate such variable you should add a modifier like "mutable" or "mut" or something. Another common trap in D coding is iterating on an array of structs with foreach, mutating the current struct and forgetting that you are mutating only a _copy_ of the items. Unfortunately there is no mutable keyword in D, and Walter rejected all this idea. So the next best thing it to always put "immutable" at the foreach variable, unless you want to mutate it or if you can't use const/immutable for some other reason.

Probably I can invent you more creative answers if you want.

Bear hugs,
bearophile
« First   ‹ Prev
1 2