{
  "id": "P060",
  "slug": "probability-lab",
  "key": "P060-probability-lab",
  "title": "Probability lab",
  "summary": "The birthday problem, the Monty Hall game with a host who knows and a host who guesses, and the sum of two dice - each worked out exactly as a fraction and then tried thousands of times on EML's own random numbers, side by side.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P060 - Probability lab\n\nThree classic experiments, each worked out exactly and then tried many\ntimes with random numbers, side by side: the birthday problem, the Monty\nHall game with two kinds of host, and the sum of two dice.\n\n- `main.eml` - the menu, the experiments' questions and results\n- `lab.eml` - the exact answers and the simulations\n- `frac.eml` - exact fractions, from P007 by way of P038, P044 and P056\n- `rng.eml` - random numbers written in EML, the generator of P008\n\nHow each part works:\n\n- Birthdays: the chance that n people all have different birthdays is\n  365/365 x 364/365 x ... x (366 - n)/365. The chance that two share one is\n  1 minus that. It is kept as one exact fraction, not reduced: its numbers\n  reach 60 digits, and only showing the percentage needs a division. The\n  smallest group where a shared birthday is more likely than not, 23, is\n  found by comparing 2 x shared with all, without dividing.\n- Monty Hall: the car is behind one of three doors, and you pick one.\n  - The usual host knows where the car is and opens another door with a\n    goat. Staying wins 1/3 of the time, switching 2/3.\n  - A host who opens one of the other two doors at random sometimes shows\n    the car. Counting only the games where he showed a goat, staying and\n    switching each win 1/2.\n  The doors and the goat you see are the same; what the host knew is what\n  makes switching pay.\n- Two dice: of the 36 equal throws, 6 make 7, and 1 each make 2 and 12.\n  The throws are tallied by their sum in a dict, as in the corpus case\n  `dice-roll-tally`: read the count, write it back plus one.\n- The random numbers come from EML code, so a seed gives the same\n  experiment on every machine. One generator runs through the session, so\n  the next experiment continues where the last one stopped.\n\nWhat is checked: menu choices 1 to 5; 2 to 100 people; 100 to 5000 groups,\ngames or throws; a seed from 0 to 999999999. An empty answer cancels.\n\nSessions: `sessions/basic.in` runs from the default seed 2026.\n- Birthdays for 23 people: exactly 50.73%, and 1,030 of 2,000 random\n  groups. For 50 people: 97.04%, and 478 of 500.\n- Monty Hall, 3,000 games. With the host who knows, switching won 66.23%.\n  With the random host, 2,068 games showed a goat, and switching won 52.76%\n  of those. That is 2.5 standard errors above the exact 1/2, which a run of\n  2,068 games does about once in 80. Longer runs come closer.\n- Two dice, 3,600 throws, each sum next to its exact share out of 36.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- groups of 1, 101 and x people, and an empty answer;\n- 99 and 5001 groups, and an empty answer;\n- cancelled Monty Hall and dice runs;\n- seeds -1 and abc, and an empty seed;\n- seed 7, then 5 people in 100 groups: exactly 2.71%, and 4 of 100 tried.\n\nBuilt on the verified corpus case `dice-roll-tally` (dice rolls tallied by\nface in a dict, and the most frequent face found by hand).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P060 probability lab: three classic experiments, each worked out exactly\n# and then run many times on EML's own random numbers - the birthday\n# problem, the Monty Hall game under two kinds of host, and the sum of two\n# dice.\nimport frac\nimport lab\nimport rng\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef ask_number(prompt, low, high):\n    while True:\n        trim(input(prompt + \" (\" + str(low) + \" to \" + str(high) + \")> \")) => answer\n        if answer == \"\":\n            return -1\n        True => ok\n        if len(answer) > 9:\n            False => ok\n        for c in answer:\n            if not (c >= \"0\" and c <= \"9\"):\n                False => ok\n        if ok and int(answer) >= low and int(answer) <= high:\n            return int(answer)\n        (\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\") ^0\n\ndef share(count, trials):\n    return lab.percent(frac.make(count, trials))\n\ndef birthday(state):\n    ask_number(\"people in the group\", 2, 100) => n\n    if n == -1:\n        \"Cancelled.\" ^0\n        return\n    ask_number(\"groups to try\", 100, 5000) => trials\n    if trials == -1:\n        \"Cancelled.\" ^0\n        return\n    lab.birthday_exact(n) => p\n    lab.birthday_trials(n, trials, state[0]) => hits\n    (\"Exactly: \" + lab.percent(p) + \" of groups of \" + str(n) + \" have two people sharing a birthday.\") ^0\n    (\"Tried:   \" + str(hits) + \" of \" + str(trials) + \" random groups did, \" + share(hits, trials) + \".\") ^0\n    (\"From \" + str(lab.birthday_threshold()) + \" people on, a shared birthday is more likely than not.\") ^0\n\ndef monty(state):\n    ask_number(\"games\", 100, 5000) => trials\n    if trials == -1:\n        \"Cancelled.\" ^0\n        return\n    lab.monty_trials(trials, state[0], False) => a\n    \"A host who knows where the car is always opens a door with a goat:\" ^0\n    (\"  staying won \" + str(a[1]) + \" of \" + str(a[0]) + \" (\" + share(a[1], a[0]) + \"), switching \" + str(a[2]) + \" (\" + share(a[2], a[0]) + \"); exactly 1/3 and 2/3.\") ^0\n    lab.monty_trials(trials, state[0], True) => b\n    \"A host who opens one of the other doors at random, counting only the games where that door had a goat:\" ^0\n    (\"  \" + str(b[0]) + \" of \" + str(trials) + \" games counted; staying won \" + str(b[1]) + \" (\" + share(b[1], b[0]) + \"), switching \" + str(b[2]) + \" (\" + share(b[2], b[0]) + \"); exactly 1/2 each.\") ^0\n    \"Same doors, same goat shown - what the host knew is what makes switching pay.\" ^0\n\ndef dice(state):\n    ask_number(\"throws\", 100, 5000) => trials\n    if trials == -1:\n        \"Cancelled.\" ^0\n        return\n    lab.dice_exact() => ways\n    lab.dice_trials(trials, state[0]) => tally\n    \"sum  exactly       thrown\" ^0\n    for s in [2:12]:\n        str(s) => a\n        if len(a) == 1:\n            \" \" + a => a\n        lab.percent(frac.make(ways[s], 36)) => e\n        while len(e) < 7:\n            \" \" + e => e\n        str(tally[s]) => c\n        while len(c) < 5:\n            \" \" + c => c\n        share(tally[s], trials) => f\n        while len(f) < 7:\n            \" \" + f => f\n        int((tally[s] * 100 + trials) / (2 * trials)) => marks\n        (\" \" + a + \"  \" + str(ways[s]) + \"/36 \" + e + \"  \" + c + \" \" + f + \"  \" + \"#\" * marks) ^0\n\ndef set_seed(state):\n    ask_number(\"seed\", 0, 999999999) => seed\n    if seed == -1:\n        \"Cancelled.\" ^0\n        return\n    rng.Rng(seed) => state[0]\n    (\"The experiments now draw from seed \" + str(seed) + \".\") ^0\n\n\"== Probability lab ==\" ^0\n\"Each experiment is worked out exactly, then tried many times with random numbers.\" ^0\n# [the generator]\n[rng.Rng(2026)] => state\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        birthday(state)\n    elif choice == \"2\":\n        monty(state)\n    elif choice == \"3\":\n        dice(state)\n    elif choice == \"4\":\n        set_seed(state)\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import frac\nimport lab\nimport rng\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef ask_number(prompt, low, high):\n    while True:\n        answer = trim(input(prompt + \" (\" + str(low) + \" to \" + str(high) + \")> \"))\n        if answer == \"\":\n            return -1\n        ok = True\n        if len(answer) > 9:\n            ok = False\n        for c in answer:\n            if not (c >= \"0\" and c <= \"9\"):\n                ok = False\n        if ok and int(answer) >= low and int(answer) <= high:\n            return int(answer)\n        print(\"Type a whole number from \" + str(low) + \" to \" + str(high) + \".\")\n\ndef share(count, trials):\n    return lab.percent(frac.make(count, trials))\n\ndef birthday(state):\n    n = ask_number(\"people in the group\", 2, 100)\n    if n == -1:\n        print(\"Cancelled.\")\n        return\n    trials = ask_number(\"groups to try\", 100, 5000)\n    if trials == -1:\n        print(\"Cancelled.\")\n        return\n    p = lab.birthday_exact(n)\n    hits = lab.birthday_trials(n, trials, state[0])\n    print(\"Exactly: \" + lab.percent(p) + \" of groups of \" + str(n) + \" have two people sharing a birthday.\")\n    print(\"Tried:   \" + str(hits) + \" of \" + str(trials) + \" random groups did, \" + share(hits, trials) + \".\")\n    print(\"From \" + str(lab.birthday_threshold()) + \" people on, a shared birthday is more likely than not.\")\n\ndef monty(state):\n    trials = ask_number(\"games\", 100, 5000)\n    if trials == -1:\n        print(\"Cancelled.\")\n        return\n    a = lab.monty_trials(trials, state[0], False)\n    print(\"A host who knows where the car is always opens a door with a goat:\")\n    print(\"  staying won \" + str(a[1]) + \" of \" + str(a[0]) + \" (\" + share(a[1], a[0]) + \"), switching \" + str(a[2]) + \" (\" + share(a[2], a[0]) + \"); exactly 1/3 and 2/3.\")\n    b = lab.monty_trials(trials, state[0], True)\n    print(\"A host who opens one of the other doors at random, counting only the games where that door had a goat:\")\n    print(\"  \" + str(b[0]) + \" of \" + str(trials) + \" games counted; staying won \" + str(b[1]) + \" (\" + share(b[1], b[0]) + \"), switching \" + str(b[2]) + \" (\" + share(b[2], b[0]) + \"); exactly 1/2 each.\")\n    print(\"Same doors, same goat shown - what the host knew is what makes switching pay.\")\n\ndef dice(state):\n    trials = ask_number(\"throws\", 100, 5000)\n    if trials == -1:\n        print(\"Cancelled.\")\n        return\n    ways = lab.dice_exact()\n    tally = lab.dice_trials(trials, state[0])\n    print(\"sum  exactly       thrown\")\n    for s in range(2, 13):\n        a = str(s)\n        if len(a) == 1:\n            a = \" \" + a\n        e = lab.percent(frac.make(ways[s], 36))\n        while len(e) < 7:\n            e = \" \" + e\n        c = str(tally[s])\n        while len(c) < 5:\n            c = \" \" + c\n        f = share(tally[s], trials)\n        while len(f) < 7:\n            f = \" \" + f\n        marks = int((tally[s] * 100 + trials) / (2 * trials))\n        print(\" \" + a + \"  \" + str(ways[s]) + \"/36 \" + e + \"  \" + c + \" \" + f + \"  \" + \"#\" * marks)\n\ndef set_seed(state):\n    seed = ask_number(\"seed\", 0, 999999999)\n    if seed == -1:\n        print(\"Cancelled.\")\n        return\n    state[0] = rng.Rng(seed)\n    print(\"The experiments now draw from seed \" + str(seed) + \".\")\n\nprint(\"== Probability lab ==\")\nprint(\"Each experiment is worked out exactly, then tried many times with random numbers.\")\nstate = [rng.Rng(2026)]\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        birthday(state)\n    elif choice == \"2\":\n        monty(state)\n    elif choice == \"3\":\n        dice(state)\n    elif choice == \"4\":\n        set_seed(state)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "lab.eml",
      "eml": "# P060 probability lab - each experiment worked out exactly, as a fraction,\n# and run many times on EML's own random numbers.\nimport frac\n\ndef percent(x):\n    # A fraction [numerator, denominator] from 0 to 1 as a percentage, two\n    # decimal places. The fraction need not be in lowest terms.\n    frac.decimal([x[0] * 100, x[1]], 2) => s\n    if len(s) > 6 and s[0:6] == \"about \":\n        s[6:len(s)] => s\n    # always two places, so the columns line up: 2.5 -> 2.50, 50 -> 50.00\n    0 => k\n    while k < len(s) and s[k] != \".\":\n        k + 1 => k\n    if k == len(s):\n        s + \".00\" => s\n    elif len(s) - k == 2:\n        s + \"0\" => s\n    return s + \"%\"\n\ndef birthday_exact(n):\n    # The chance that among n people (365 equally likely birthdays, no 29\n    # February) at least two share one: 1 minus the chance that all differ,\n    # 365/365 x 364/365 x ... x (366 - n)/365. Kept as one exact fraction,\n    # not reduced: the numbers reach 60 digits, and reducing them takes long\n    # division, which only showing the result needs, once.\n    1 => differ\n    1 => all\n    for k in [0:n - 1]:\n        differ * (365 - k) => differ\n        all * 365 => all\n    return [all - differ, all]\n\ndef birthday_threshold():\n    # The smallest group where a shared birthday is more likely than not:\n    # shared / all >= 1/2, compared as 2 x shared >= all - no division.\n    1 => n\n    birthday_exact(n) => p\n    while 2 * p[0] < p[1]:\n        n + 1 => n\n        birthday_exact(n) => p\n    return n\n\ndef birthday_trials(n, trials, g):\n    # How many of `trials` random groups of n have a shared birthday.\n    0 => shared\n    for t in [1:trials]:\n        [False] * 365 => seen\n        False => hit\n        for k in [1:n]:\n            g.below(365) => day\n            if seen[day]:\n                True => hit\n            True => seen[day]\n        if hit:\n            shared + 1 => shared\n    return shared\n\ndef monty_trials(trials, g, host_random):\n    # [games counted, wins by staying, wins by switching]. The car is behind\n    # one of three doors and you pick one. The usual host knows where the car\n    # is and opens another door with a goat (choosing at random when both\n    # are goats). A host who opens one of the other two doors at random\n    # sometimes shows the car; those games are not counted, so the rest are\n    # the games where he happened to show a goat.\n    0 => counted\n    0 => stay\n    0 => switch\n    for t in [1:trials]:\n        g.below(3) => car\n        g.below(3) => pick\n        # one more draw, used by either host to choose between two doors\n        g.below(2) => r\n        # the two doors you did not pick, in order\n        [] => others\n        for d in [0:2]:\n            if d != pick:\n                others + [d] => others\n        if host_random:\n            others[r] => opened\n        elif others[0] == car:\n            others[1] => opened\n        elif others[1] == car:\n            others[0] => opened\n        else:\n            others[r] => opened\n        if opened != car:\n            counted + 1 => counted\n            if pick == car:\n                stay + 1 => stay\n            else:\n                switch + 1 => switch\n    return [counted, stay, switch]\n\ndef dice_exact():\n    # How many of the 36 equally likely throws of two dice give each sum,\n    # sums 2 to 12 at indexes 2 to 12.\n    [0] * 13 => ways\n    for a in [1:6]:\n        for b in [1:6]:\n            ways[a + b] + 1 => ways[a + b]\n    return ways\n\ndef dice_trials(trials, g):\n    # Throws of two dice tallied by their sum in a dict, as the corpus case\n    # dice-roll-tally tallies faces: read the count, write it back plus one.\n    {} => tally\n    for s in [2:12]:\n        0 => tally[s]\n    for t in [1:trials]:\n        g.below(6) + 1 + g.below(6) + 1 => s\n        tally[s] + 1 => tally[s]\n    return tally\n",
      "python": "import frac\n\ndef percent(x):\n    s = frac.decimal([x[0] * 100, x[1]], 2)\n    if len(s) > 6 and s[0:6] == \"about \":\n        s = s[6:len(s)]\n    k = 0\n    while k < len(s) and s[k] != \".\":\n        k = k + 1\n    if k == len(s):\n        s = s + \".00\"\n    elif len(s) - k == 2:\n        s = s + \"0\"\n    return s + \"%\"\n\ndef birthday_exact(n):\n    differ = 1\n    all = 1\n    for k in range(0, n):\n        differ = differ * (365 - k)\n        all = all * 365\n    return [all - differ, all]\n\ndef birthday_threshold():\n    n = 1\n    p = birthday_exact(n)\n    while 2 * p[0] < p[1]:\n        n = n + 1\n        p = birthday_exact(n)\n    return n\n\ndef birthday_trials(n, trials, g):\n    shared = 0\n    for t in range(1, trials+1):\n        seen = [False] * 365\n        hit = False\n        for k in range(1, n+1):\n            day = g.below(365)\n            if seen[day]:\n                hit = True\n            seen[day] = True\n        if hit:\n            shared = shared + 1\n    return shared\n\ndef monty_trials(trials, g, host_random):\n    counted = 0\n    stay = 0\n    switch = 0\n    for t in range(1, trials+1):\n        car = g.below(3)\n        pick = g.below(3)\n        r = g.below(2)\n        others = []\n        for d in range(0, 3):\n            if d != pick:\n                others = others + [d]\n        if host_random:\n            opened = others[r]\n        elif others[0] == car:\n            opened = others[1]\n        elif others[1] == car:\n            opened = others[0]\n        else:\n            opened = others[r]\n        if opened != car:\n            counted = counted + 1\n            if pick == car:\n                stay = stay + 1\n            else:\n                switch = switch + 1\n    return [counted, stay, switch]\n\ndef dice_exact():\n    ways = [0] * 13\n    for a in range(1, 7):\n        for b in range(1, 7):\n            ways[a + b] = ways[a + b] + 1\n    return ways\n\ndef dice_trials(trials, g):\n    tally = {}\n    for s in range(2, 13):\n        tally[s] = 0\n    for t in range(1, trials+1):\n        s = g.below(6) + 1 + g.below(6) + 1\n        tally[s] = tally[s] + 1\n    return tally\n"
    },
    {
      "name": "frac.eml",
      "eml": "# P060 probability lab - exact fractions, from P007 (calculator) by way of\n# P038, P044 and P056.\n# A number is a list [n, d]: n / d in lowest terms with d > 0. Integers have\n# no size limit, but EML has no //, and a / b goes through a float that cannot\n# hold a large quotient exactly, so whole-number division is written out.\n\ndef quotient(a, b):\n    # a // b for whole numbers a >= 0 and b > 0, exact at any size. Long\n    # division by doubling: take away the largest b * 2^k that still fits.\n    0 => q\n    while a >= b:\n        b => m\n        1 => k\n        while m + m <= a:\n            m + m => m\n            k + k => k\n        a - m => a\n        q + k => q\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        a % b => r\n        b => a\n        r => b\n    return a\n\ndef make(n, d):\n    # n / d in lowest terms with a positive denominator (d != 0).\n    if d < 0:\n        0 - n => n\n        0 - d => d\n    abs(n) => a\n    gcd(a, d) => g\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    # x / y; the caller has checked that y is not zero.\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    # The value of a string of 1 to 9 digits, otherwise -1.\n    if s == \"\" or len(s) > 9:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef parse(s):\n    # \"3\", \"-2\", \"3/4\", \"-0.25\" as a fraction; [] if it is not a number.\n    1 => sign\n    if len(s) > 0 and s[0] == \"-\":\n        -1 => sign\n        s[1:len(s)] => s\n    0 => k\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k + 1 => k\n    if k == len(s):\n        digits_value(s) => n\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    digits_value(s[0:k]) => whole\n    s[k + 1:len(s)] => rest\n    digits_value(rest) => part\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    # a decimal point: 0.25 is 25 / 100\n    1 => scale\n    for c in rest:\n        scale * 10 => scale\n    return make(sign * (whole * scale + part), scale)\n\ndef less(x, y):\n    return x[0] * y[1] < y[0] * x[1]\n\ndef decimal(x, places):\n    # x to the given number of decimal places, halves rounded away from zero,\n    # with \"about \" in front when the value does not end there exactly.\n    \"\" => sign\n    abs(x[0]) => n\n    if x[0] < 0:\n        \"-\" => sign\n    1 => scale\n    for k in [1:places]:\n        scale * 10 => scale\n    quotient(2 * n * scale + x[1], 2 * x[1]) => t\n    \"\" => about\n    if (n * scale) % x[1] != 0:\n        \"about \" => about\n    str(quotient(t, scale)) => whole\n    str(t % scale) => part\n    while len(part) < places:\n        \"0\" + part => part\n    # drop trailing zeros of an exact value\n    if about == \"\":\n        while len(part) > 0 and part[len(part) - 1] == \"0\":\n            part[0:len(part) - 1] => part\n    if t == 0:\n        \"\" => sign\n    if part == \"\":\n        return about + sign + whole\n    return about + sign + whole + \".\" + part\n\ndef text(x):\n    # \"3\", \"-3\", \"3/4\", \"-3/4\".\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n",
      "python": "def quotient(a, b):\n    q = 0\n    while a >= b:\n        m = b\n        k = 1\n        while m + m <= a:\n            m = m + m\n            k = k + k\n        a = a - m\n        q = q + k\n    return q\n\ndef gcd(a, b):\n    while b != 0:\n        r = a % b\n        a = b\n        b = r\n    return a\n\ndef make(n, d):\n    if d < 0:\n        n = 0 - n\n        d = 0 - d\n    a = abs(n)\n    g = gcd(a, d)\n    if n < 0:\n        return [0 - quotient(a, g), quotient(d, g)]\n    return [quotient(a, g), quotient(d, g)]\n\ndef plus(x, y):\n    return make(x[0] * y[1] + y[0] * x[1], x[1] * y[1])\n\ndef minus(x, y):\n    return make(x[0] * y[1] - y[0] * x[1], x[1] * y[1])\n\ndef times(x, y):\n    return make(x[0] * y[0], x[1] * y[1])\n\ndef over(x, y):\n    return make(x[0] * y[1], x[1] * y[0])\n\ndef is_zero(x):\n    return x[0] == 0\n\ndef digits_value(s):\n    if s == \"\" or len(s) > 9:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef parse(s):\n    sign = 1\n    if len(s) > 0 and s[0] == \"-\":\n        sign = -1\n        s = s[1:len(s)]\n    k = 0\n    while k < len(s) and s[k] != \"/\" and s[k] != \".\":\n        k = k + 1\n    if k == len(s):\n        n = digits_value(s)\n        if n < 0:\n            return []\n        return make(sign * n, 1)\n    whole = digits_value(s[0:k])\n    rest = s[k + 1:len(s)]\n    part = digits_value(rest)\n    if whole < 0 or part < 0:\n        return []\n    if s[k] == \"/\":\n        if part == 0:\n            return []\n        return make(sign * whole, part)\n    scale = 1\n    for c in rest:\n        scale = scale * 10\n    return make(sign * (whole * scale + part), scale)\n\ndef less(x, y):\n    return x[0] * y[1] < y[0] * x[1]\n\ndef decimal(x, places):\n    sign = \"\"\n    n = abs(x[0])\n    if x[0] < 0:\n        sign = \"-\"\n    scale = 1\n    for k in range(1, places+1):\n        scale = scale * 10\n    t = quotient(2 * n * scale + x[1], 2 * x[1])\n    about = \"\"\n    if n * scale % x[1] != 0:\n        about = \"about \"\n    whole = str(quotient(t, scale))\n    part = str(t % scale)\n    while len(part) < places:\n        part = \"0\" + part\n    if about == \"\":\n        while len(part) > 0 and part[len(part) - 1] == \"0\":\n            part = part[0:len(part) - 1]\n    if t == 0:\n        sign = \"\"\n    if part == \"\":\n        return about + sign + whole\n    return about + sign + whole + \".\" + part\n\ndef text(x):\n    if x[1] == 1:\n        return str(x[0])\n    return str(x[0]) + \"/\" + str(x[1])\n"
    },
    {
      "name": "rng.eml",
      "eml": "# P060 probability lab - random numbers written in EML: the linear congruential\n# generator of P008 (number guessing), with the constants of the C\n# standard's example rand(). Python's random module is not used, so a seed\n# gives the same experiment on every machine, and the interpreter can check a\n# whole session byte for byte.\n\nclass Rng:\n    def __init__(self, seed):\n        seed % 2147483648 => self.state\n\n    def step(self):\n        (1103515245 * self.state + 12345) % 2147483648 => self.state\n        return self.state\n\n    def below(self, n):\n        # A number from 0 to n - 1, taken from the high bits of the state: the\n        # low bits of this generator repeat with short periods. Dividing by\n        # 65536 is exact in a float for a state below 2^31.\n        return int(self.step() / 65536) % n\n",
      "python": "class Rng:\n    def __init__(self, seed):\n        self.state = seed % 2147483648\n    def step(self):\n        self.state = (1103515245 * self.state + 12345) % 2147483648\n        return self.state\n    def below(self, n):\n        return int(self.step() / 65536) % n\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\n1\n101\nx\n\n1\n10\n99\n5001\n\n2\n\n3\n\n4\n-1\nabc\n\n4\n7\n1\n5\n100\n5\n",
      "screen": "== Probability lab ==\nEach experiment is worked out exactly, then tried many times with random numbers.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 1\npeople in the group (2 to 100)> 1\nType a whole number from 2 to 100.\npeople in the group (2 to 100)> 101\nType a whole number from 2 to 100.\npeople in the group (2 to 100)> x\nType a whole number from 2 to 100.\npeople in the group (2 to 100)> \nCancelled.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 1\npeople in the group (2 to 100)> 10\ngroups to try (100 to 5000)> 99\nType a whole number from 100 to 5000.\ngroups to try (100 to 5000)> 5001\nType a whole number from 100 to 5000.\ngroups to try (100 to 5000)> \nCancelled.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 2\ngames (100 to 5000)> \nCancelled.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 3\nthrows (100 to 5000)> \nCancelled.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 4\nseed (0 to 999999999)> -1\nType a whole number from 0 to 999999999.\nseed (0 to 999999999)> abc\nType a whole number from 0 to 999999999.\nseed (0 to 999999999)> \nCancelled.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 4\nseed (0 to 999999999)> 7\nThe experiments now draw from seed 7.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 1\npeople in the group (2 to 100)> 5\ngroups to try (100 to 5000)> 100\nExactly: 2.71% of groups of 5 have two people sharing a birthday.\nTried:   4 of 100 random groups did, 4.00%.\nFrom 23 people on, a shared birthday is more likely than not.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n23\n2000\n1\n50\n500\n2\n3000\n3\n3600\n5\n",
      "screen": "== Probability lab ==\nEach experiment is worked out exactly, then tried many times with random numbers.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 1\npeople in the group (2 to 100)> 23\ngroups to try (100 to 5000)> 2000\nExactly: 50.73% of groups of 23 have two people sharing a birthday.\nTried:   1030 of 2000 random groups did, 51.50%.\nFrom 23 people on, a shared birthday is more likely than not.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 1\npeople in the group (2 to 100)> 50\ngroups to try (100 to 5000)> 500\nExactly: 97.04% of groups of 50 have two people sharing a birthday.\nTried:   478 of 500 random groups did, 95.60%.\nFrom 23 people on, a shared birthday is more likely than not.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 2\ngames (100 to 5000)> 3000\nA host who knows where the car is always opens a door with a goat:\n  staying won 1013 of 3000 (33.77%), switching 1987 (66.23%); exactly 1/3 and 2/3.\nA host who opens one of the other doors at random, counting only the games where that door had a goat:\n  2068 of 3000 games counted; staying won 977 (47.24%), switching 1091 (52.76%); exactly 1/2 each.\nSame doors, same goat shown - what the host knew is what makes switching pay.\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 3\nthrows (100 to 5000)> 3600\nsum  exactly       thrown\n  2  1/36   2.78%     90   2.50%  #\n  3  2/36   5.56%    194   5.39%  ###\n  4  3/36   8.33%    307   8.53%  ####\n  5  4/36  11.11%    402  11.17%  ######\n  6  5/36  13.89%    521  14.47%  #######\n  7  6/36  16.67%    586  16.28%  ########\n  8  5/36  13.89%    500  13.89%  #######\n  9  4/36  11.11%    387  10.75%  #####\n 10  3/36   8.33%    313   8.69%  ####\n 11  2/36   5.56%    200   5.56%  ###\n 12  1/36   2.78%    100   2.78%  #\n\n1) birthdays  2) Monty Hall  3) two dice  4) seed  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "dice-roll-tally",
      "caseId": "018-dice-roll-tally",
      "title": "Case corpus: a self-authored dice roll tally"
    }
  ],
  "updated": "2026-10-10"
}
