Skip to content

Commit

Permalink
Fix signed-integer mapping algorithm mistakes
Browse files Browse the repository at this point in the history
These new algorithms aren't ideal,
but at least they respect the user's range boundaries!
  • Loading branch information
JanCVanB committed Jan 20, 2022
1 parent 8e97c17 commit 09117cf
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 36 deletions.
37 changes: 30 additions & 7 deletions Random.roc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ toI16 = \n -> n |> Num.toStr |> Str.toI16 |> Result.withDefault 0
toU16 = \n -> n |> Num.toStr |> Str.toU16 |> Result.withDefault 0
toI32 = \n -> n |> Num.toStr |> Str.toI32 |> Result.withDefault 0
toU32 = \n -> n |> Num.toStr |> Str.toU32 |> Result.withDefault 0
toI64 = \n -> n |> Num.toStr |> Str.toI64 |> Result.withDefault 0


## # Types
Expand Down Expand Up @@ -101,40 +102,62 @@ int = i32
## A [Generator] for 8-bit signed integers between two boundaries (inclusive)
i8 : I8, I8 -> Generator Seed8 I8
i8 = \x, y ->
between x y (\s -> mapToI8 (growSeed8 s)) (\s -> updateSeed8 s)
Pair minimum maximum = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> toI64
\s ->
# TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
offset = growSeed8 s |> mapToI8 |> toI64 |> Num.sub (toI64 minI8) |> modWithNonzero range
value = minimum |> toI64 |> Num.add offset |> toI8
{ value, seed: updateSeed8 s }

## A [Generator] for 16-bit signed integers between two boundaries (inclusive)
i16 : I16, I16 -> Generator Seed16 I16
i16 = \x, y ->
between x y (\s -> mapToI16 (growSeed16 s)) (\s -> updateSeed16 s)
Pair minimum maximum = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> toI64
\s ->
# TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
offset = growSeed16 s |> mapToI16 |> toI64 |> Num.sub (toI64 minI16) |> modWithNonzero range
value = minimum |> toI64 |> Num.add offset |> toI16
{ value, seed: updateSeed16 s }

## A [Generator] for 32-bit signed integers between two boundaries (inclusive)
i32 : I32, I32 -> Generator Seed32 I32
i32 = \x, y ->
between x y (\s -> mapToI32 (growSeed32 s)) (\s -> updateSeed32 s)
Pair minimum maximum = sort x y
# TODO: Remove these `I64` dependencies.
range = maximum - minimum + 1 |> toI64
\s ->
# TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
offset = growSeed32 s |> mapToI32 |> toI64 |> Num.sub (toI64 Num.minI32) |> modWithNonzero range
value = minimum |> toI64 |> Num.add offset |> toI32
{ value, seed: updateSeed32 s }

## A [Generator] for 8-bit unsigned integers between two boundaries (inclusive)
u8 : U8, U8 -> Generator Seed8 U8
u8 = \x, y ->
between x y (\s -> growSeed8 s) (\s -> updateSeed8 s)
betweenUnsigned x y (\s -> growSeed8 s) (\s -> updateSeed8 s)

## A [Generator] for 16-bit unsigned integers between two boundaries (inclusive)
u16 : U16, U16 -> Generator Seed16 U16
u16 = \x, y ->
between x y (\s -> growSeed16 s) (\s -> updateSeed16 s)
betweenUnsigned x y (\s -> growSeed16 s) (\s -> updateSeed16 s)

## A [Generator] for 32-bit unsigned integers between two boundaries (inclusive)
u32 : U32, U32 -> Generator Seed32 U32
u32 = \x, y ->
between x y (\s -> growSeed32 s) (\s -> updateSeed32 s)
betweenUnsigned x y (\s -> growSeed32 s) (\s -> updateSeed32 s)


#### Helpers for the above constructors

between = \x, y, growSeed, updateSeed ->
betweenUnsigned = \x, y, growSeed, updateSeed ->
Pair minimum maximum = sort x y
range = maximum - minimum + 1
\s ->
# TODO: Analyze this. The mod-ing might be biased towards a smaller offset!
value = minimum + modWithNonzero (growSeed s) range
{ value, seed: updateSeed s }

Expand Down
36 changes: 18 additions & 18 deletions digits.example.roc
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,22 @@ main =
q = p |> Random.next (Random.u8 0 9)
r = p |> Random.next (Random.u8 0 9)

_ <- await (line (Num.toStr a.value |> \s -> "a == -9 == \(s)"))
_ <- await (line (Num.toStr b.value |> \s -> "b == -9 == \(s)"))
_ <- await (line (Num.toStr c.value |> \s -> "c == 9 == \(s)"))
_ <- await (line (Num.toStr d.value |> \s -> "d == 9 == \(s)"))
_ <- await (line (Num.toStr e.value |> \s -> "e == -6 == \(s)"))
_ <- await (line (Num.toStr f.value |> \s -> "f == -6 == \(s)"))
_ <- await (line (Num.toStr g.value |> \s -> "g == 2 == \(s)"))
_ <- await (line (Num.toStr h.value |> \s -> "h == 2 == \(s)"))
_ <- await (line (Num.toStr i.value |> \s -> "i == -2 == \(s)"))
_ <- await (line (Num.toStr j.value |> \s -> "j == -2 == \(s)"))
_ <- await (line (Num.toStr k.value |> \s -> "k == 6 == \(s)"))
_ <- await (line (Num.toStr l.value |> \s -> "l == 6 == \(s)"))
_ <- await (line (Num.toStr m.value |> \s -> "m == 3 == \(s)"))
_ <- await (line (Num.toStr n.value |> \s -> "n == 3 == \(s)"))
_ <- await (line (Num.toStr o.value |> \s -> "o == 7 == \(s)"))
_ <- await (line (Num.toStr p.value |> \s -> "p == 7 == \(s)"))
_ <- await (line (Num.toStr q.value |> \s -> "q == 0 == \(s)"))
_ <- await (line (Num.toStr r.value |> \s -> "r == 0 == \(s)"))
_ <- await (line (Num.toStr a.value |> \s -> "a == 9 == \(s)"))
_ <- await (line (Num.toStr b.value |> \s -> "b == 9 == \(s)"))
_ <- await (line (Num.toStr c.value |> \s -> "c == 9 == \(s)"))
_ <- await (line (Num.toStr d.value |> \s -> "d == 9 == \(s)"))
_ <- await (line (Num.toStr e.value |> \s -> "e == 6 == \(s)"))
_ <- await (line (Num.toStr f.value |> \s -> "f == 6 == \(s)"))
_ <- await (line (Num.toStr g.value |> \s -> "g == 2 == \(s)"))
_ <- await (line (Num.toStr h.value |> \s -> "h == 2 == \(s)"))
_ <- await (line (Num.toStr i.value |> \s -> "i == 2 == \(s)"))
_ <- await (line (Num.toStr j.value |> \s -> "j == 2 == \(s)"))
_ <- await (line (Num.toStr k.value |> \s -> "k == 6 == \(s)"))
_ <- await (line (Num.toStr l.value |> \s -> "l == 6 == \(s)"))
_ <- await (line (Num.toStr m.value |> \s -> "m == 3 == \(s)"))
_ <- await (line (Num.toStr n.value |> \s -> "n == 3 == \(s)"))
_ <- await (line (Num.toStr o.value |> \s -> "o == 7 == \(s)"))
_ <- await (line (Num.toStr p.value |> \s -> "p == 7 == \(s)"))
_ <- await (line (Num.toStr q.value |> \s -> "q == 0 == \(s)"))
_ <- await (line (Num.toStr r.value |> \s -> "r == 0 == \(s)"))
line "These values will be the same on every run, because we use constant seeds."
14 changes: 7 additions & 7 deletions points.example.roc
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ app "points_example"

main =

a = pointGen (Random.seed 42)
a = pointGen (Random.seed 36)
b = a |> Random.next pointGen

_ <- await (line (Num.toStr a.value.x |> \s -> "a.x == -25 == \(s)"))
_ <- await (line (Num.toStr a.value.y |> \s -> "a.y == -74 == \(s)"))
_ <- await (line (Num.toStr b.value.x |> \s -> "b.x == -27 == \(s)"))
_ <- await (line (Num.toStr b.value.y |> \s -> "b.y == -82 == \(s)"))
line "These values will be the same on every run, because we use a constant seed (42)."
_ <- await (line (Num.toStr a.value.x |> \s -> "a.x == 24 == \(s)"))
_ <- await (line (Num.toStr a.value.y |> \s -> "a.y == 37 == \(s)"))
_ <- await (line (Num.toStr b.value.x |> \s -> "b.x == 61 == \(s)"))
_ <- await (line (Num.toStr b.value.y |> \s -> "b.y == -47 == \(s)"))
line "These values will be the same on every run, because we use a constant seed (36)."


Point a : { x : a, y : a }
Expand All @@ -25,7 +25,7 @@ pointGen : Random.Generator Random.Seed32 (Point I32)
pointGen = \seed ->
# TODO: remove unnecessary type definitions (min: U32...) once #2336 is fixed
min: I32
min = 0
min = -100
max: I32
max = 100

Expand Down
8 changes: 4 additions & 4 deletions simple.example.roc
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ main =

seed = Random.seed 42 # `seed` stores the "randomness", initialized by the user/platform.
int = Random.int 0 100 # `int` generates values from 0-100 (inclusive) and updates the seed.
x = int seed # x == { value: -25, seed: Seed32 -60952905 }
y = x |> Random.next int # y == { value: -74, seed: Seed32 1561666408 }
x = int seed # x == { value: 9, seed: Seed32 -60952905 }
y = x |> Random.next int # y == { value: 61, seed: Seed32 1561666408 }

_ <- await (line (Num.toStr x.value |> \s -> "x: \(s)")) # This will print `x: -25`.
_ <- await (line (Num.toStr y.value |> \s -> "y: \(s)")) # This will print `x: -74`.
_ <- await (line (Num.toStr x.value |> \s -> "x: \(s)")) # This will print `x: 9`.
_ <- await (line (Num.toStr y.value |> \s -> "y: \(s)")) # This will print `x: 61`.
line "These values will be the same on every run, because we use a constant seed (42)."

0 comments on commit 09117cf

Please sign in to comment.