Leaderboard
A small league: games give points and move Elo ratings, worked out in whole numbers with an exact 400th root; the standings rank players level on points the five usual ways - competition, modified competition, dense, ordinal and fractional.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
A small league. A game gives 3 points for a win and 1 each for a draw, and moves both players' Elo ratings. The standings show the five usual ways to rank players who are level on points. The program can also say what a game between two players is worth before it is played. It starts with seven players, three of them level on 7 points.
main.eml- the menu, the standings table, recording games, and new playersranking.eml- the five rankings, and Elo in whole numbers
How each part works:
- Rankings, for the scores 9, 7, 7, 7, 4, 4, 1: - competition, "1224": 1 + how many scores are higher, as in the corpus case
rank-with-ties- 1, 2, 2, 2, 5, 5, 7; - modified competition, "1334": how many scores are at least this one - 1, 4, 4, 4, 6, 6, 7; - dense, "1223": 1 + how many different scores are higher, also from the corpus case - 1, 2, 2, 2, 3, 3, 4; - ordinal, "1234": level players in the order they joined; - fractional: the average of the places level players share, from the competition rank to the modified one - 1, 3, 3, 3, 5.5, 5.5, 7. - Elo: A's expected score against B is E = 1 / (1 + 10^((B - A)/400)), and A's rating moves by K x (score - E) with K = 32, rounded to a whole number, halves away from zero; B moves by the opposite.
- 10^(d/400) is worked out without floats: d = 400 k + r, and floor(10^(12 + r/400)) is the whole number y with y^400 <= 10^(4800 + r) < (y + 1)^400, found by halving the range from 10^12 to 10^13. That is 12 correct digits. The expected score (shown to a tenth of a percent) and the change are then exact divisions of whole numbers. Against 60-digit decimals, every rating difference from -3000 to 3000, with every result, comes out the same; no change there comes within 0.0001 of a half, far more than the method's error.
What is checked: menu choices 1 to 5; players who are in the league, and not the same player twice; a result of A, B or draw; a new name of letters, up to 10, not already used; a starting rating from 100 to 3000. An empty answer cancels.
Sessions: sessions/basic.in gives: - the standings: Ben, Chloe and Dev level on 7, Eli and Fay on 4; - what Ana against Gus is worth: 78.0%, so a win gains only 7 and a loss costs 25; - Dev beating Ana (+22 each way), and Ben drawing with Chloe (+1 to Ben, the lower rated); - the new standings, Hana joining at 1500, and what Hana against Ana is worth.
sessions/bad-input.in gives: - menu choices 0 and x; - an unknown player, a player against themselves, and the result "win", then a cancel; - a name already taken and "Ana Lee"; ratings 50, 3001 and abc, then a cancel; - a cancelled question.
Built on the verified corpus case rank-with-ties (competition and dense ranking by counting, so ties need no special case).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Leaderboard ==
A small league: 3 points for a win, 1 each for a draw, and Elo ratings with K = 32.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 0
Pick a number from 1 to 5.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> x
Pick a number from 1 to 5.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 2
player A> Zed
Nobody called Zed is in the league.
player A> Ana
player B> Ana
A player cannot play themselves.
player B> Ben
result (A, B or draw)> win
Type A, B or draw.
result (A, B or draw)>
Cancelled.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 4
name> Ana
Ana is in the league already.
name> Ana Lee
Type a name of letters, up to 10.
name> Kim
starting rating (100 to 3000)> 50
Type a whole number from 100 to 3000.
starting rating (100 to 3000)> 3001
Type a whole number from 100 to 3000.
starting rating (100 to 3000)> abc
Type a whole number from 100 to 3000.
starting rating (100 to 3000)>
Cancelled.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 3
player A>
Cancelled.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 5
Bye.
What was typed (20 lines)
0
x
2
Zed
Ana
Ana
Ben
win
4
Ana
Ana Lee
Kim
50
3001
abc
3
5
basic
interpreter: byte-equal== Leaderboard ==
A small league: 3 points for a win, 1 each for a draw, and Elo ratings with K = 32.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 1
player points rating 1224 1334 1223 1234 fractional
Ana 9 1620 1 1 1 1 1
Ben 7 1555 2 4 2 2 3
Chloe 7 1580 2 4 2 3 3
Dev 7 1490 2 4 2 4 3
Eli 4 1450 5 6 3 5 5.5
Fay 4 1530 5 6 3 6 5.5
Gus 1 1400 7 7 4 7 7
1224 competition: level players share the better rank, and the ranks after them are skipped.
1334 modified competition: level players share the worse rank. 1223 dense: no rank is skipped.
1234 ordinal: level players in the order they joined. Fractional: the average of the places they share.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 3
player A> Ana
player B> Gus
Ana (1620) is expected to score 78.0% against Gus (1400).
For Ana: a win +7, a draw -9, a loss -25.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 2
player A> Dev
player B> Ana
result (A, B or draw)> A
Before the game Dev (1490) was expected to score 32.1% against Ana (1620).
Dev +22 to 1512, Ana -22 to 1598.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 2
player A> Ben
player B> Chloe
result (A, B or draw)> draw
Before the game Ben (1555) was expected to score 46.4% against Chloe (1580).
Ben +1 to 1556, Chloe -1 to 1579.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 1
player points rating 1224 1334 1223 1234 fractional
Dev 10 1512 1 1 1 1 1
Ana 9 1598 2 2 2 2 2
Ben 8 1556 3 4 3 3 3.5
Chloe 8 1579 3 4 3 4 3.5
Eli 4 1450 5 6 4 5 5.5
Fay 4 1530 5 6 4 6 5.5
Gus 1 1400 7 7 5 7 7
1224 competition: level players share the better rank, and the ranks after them are skipped.
1334 modified competition: level players share the worse rank. 1223 dense: no rank is skipped.
1234 ordinal: level players in the order they joined. Fractional: the average of the places they share.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 4
name> Hana
starting rating (100 to 3000)> 1500
Hana joins on 0 points, rated 1500.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 3
player A> Hana
player B> Ana
Hana (1500) is expected to score 36.3% against Ana (1598).
For Hana: a win +20, a draw +4, a loss -12.
1) standings 2) record a game 3) what a game is worth 4) new player 5) quit
choice> 5
Bye.
What was typed (20 lines)
1
3
Ana
Gus
2
Dev
Ana
A
2
Ben
Chloe
draw
1
4
Hana
1500
3
Hana
Ana
5
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P071 leaderboard: a small league. Games give points (3 for a win, 1 each
# for a draw) and move Elo ratings; the standings show the five usual ways
# to rank players who are level on points.
import ranking
def trim(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def number(s):
# A whole number of up to 4 digits, or -1.
if len(s) == 0 or len(s) > 4:
return -1
0 => n
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n * 10 + int(c) => n
return n
def is_name(s):
if len(s) == 0 or len(s) > 10:
return False
for c in s:
if not ((c >= "a" and c <= "z") or (c >= "A" and c <= "Z")):
return False
return True
def pad(s, n):
while len(s) < n:
s + " " => s
return s
def lpad(s, n):
while len(s) < n:
" " + s => s
return s
def find(players, name):
for i in [0:len(players) - 1]:
if players[i][0] == name:
return i
return -1
def signed(n):
if n > 0:
return "+" + str(n)
return str(n)
def percent(permille):
# 640 -> "64.0%"
0 => whole
permille => r
while r >= 10:
r - 10 => r
whole + 1 => whole
return str(whole) + "." + str(r) + "%"
def by_points(players):
# Indices in table order: most points first; level players keep the
# order they joined the league.
[0] * len(players) => out
for i in [0:len(players) - 1]:
i => out[i]
for i in [1:len(out) - 1]:
out[i] => x
i - 1 => j
while j >= 0 and players[out[j]][1] < players[x][1]:
out[j] => out[j + 1]
j - 1 => j
x => out[j + 1]
return out
def standings(players):
[0] * len(players) => pts
for i in [0:len(players) - 1]:
players[i][1] => pts[i]
"player points rating 1224 1334 1223 1234 fractional" ^0
for i in by_points(players):
(pad(players[i][0], 12) + lpad(str(players[i][1]), 6) + lpad(str(players[i][2]), 8) + lpad(str(ranking.competition(pts, i)), 7) + lpad(str(ranking.modified(pts, i)), 6) + lpad(str(ranking.dense(pts, i)), 6) + lpad(str(ranking.ordinal(pts, i)), 6) + lpad(ranking.fractional(pts, i), 12)) ^0
"1224 competition: level players share the better rank, and the ranks after them are skipped." ^0
"1334 modified competition: level players share the worse rank. 1223 dense: no rank is skipped." ^0
"1234 ordinal: level players in the order they joined. Fractional: the average of the places they share." ^0
def ask_player(players, prompt):
while True:
trim(input(prompt)) => name
if name == "":
return -1
find(players, name) => i
if i >= 0:
return i
("Nobody called " + name + " is in the league.") ^0
def game(players):
ask_player(players, "player A> ") => a
if a == -1:
"Cancelled." ^0
return players
while True:
ask_player(players, "player B> ") => b
if b == -1:
"Cancelled." ^0
return players
if b != a:
while True:
trim(input("result (A, B or draw)> ")) => r
if r == "":
"Cancelled." ^0
return players
-1 => s2
if r == "A" or r == "a":
2 => s2
elif r == "B" or r == "b":
0 => s2
elif r == "draw":
1 => s2
if s2 >= 0:
players[a] => pa
players[b] => pb
ranking.elo(pa[2], pb[2], s2, 32) => e
("Before the game " + pa[0] + " (" + str(pa[2]) + ") was expected to score " + percent(e[0]) + " against " + pb[0] + " (" + str(pb[2]) + ").") ^0
0 => ga
0 => gb
if s2 == 2:
3 => ga
elif s2 == 0:
3 => gb
else:
1 => ga
1 => gb
[pa[0], pa[1] + ga, pa[2] + e[1]] => players[a]
[pb[0], pb[1] + gb, pb[2] - e[1]] => players[b]
(pa[0] + " " + signed(e[1]) + " to " + str(pa[2] + e[1]) + ", " + pb[0] + " " + signed(0 - e[1]) + " to " + str(pb[2] - e[1]) + ".") ^0
return players
"Type A, B or draw." ^0
"A player cannot play themselves." ^0
def join(players):
while True:
trim(input("name> ")) => name
if name == "":
"Cancelled." ^0
return players
if not is_name(name):
"Type a name of letters, up to 10." ^0
elif find(players, name) >= 0:
(name + " is in the league already.") ^0
else:
while True:
trim(input("starting rating (100 to 3000)> ")) => s
if s == "":
"Cancelled." ^0
return players
number(s) => r
if r >= 100 and r <= 3000:
(name + " joins on 0 points, rated " + str(r) + ".") ^0
return players + [[name, 0, r]]
"Type a whole number from 100 to 3000." ^0
def odds(players):
ask_player(players, "player A> ") => a
if a == -1:
"Cancelled." ^0
return
while True:
ask_player(players, "player B> ") => b
if b == -1:
"Cancelled." ^0
return
if b != a:
players[a] => pa
players[b] => pb
ranking.elo(pa[2], pb[2], 2, 32) => win
ranking.elo(pa[2], pb[2], 1, 32) => draw
ranking.elo(pa[2], pb[2], 0, 32) => loss
(pa[0] + " (" + str(pa[2]) + ") is expected to score " + percent(win[0]) + " against " + pb[0] + " (" + str(pb[2]) + ").") ^0
("For " + pa[0] + ": a win " + signed(win[1]) + ", a draw " + signed(draw[1]) + ", a loss " + signed(loss[1]) + ".") ^0
return
"A player cannot play themselves." ^0
[
["Ana", 9, 1620],
["Ben", 7, 1555],
["Chloe", 7, 1580],
["Dev", 7, 1490],
["Eli", 4, 1450],
["Fay", 4, 1530],
["Gus", 1, 1400],
] => players
"== Leaderboard ==" ^0
"A small league: 3 points for a win, 1 each for a draw, and Elo ratings with K = 32." ^0
True => running
while running:
"" ^0
"1) standings 2) record a game 3) what a game is worth 4) new player 5) quit" ^0
trim(input("choice> ")) => choice
if choice == "1":
standings(players)
elif choice == "2":
game(players) => players
elif choice == "3":
odds(players)
elif choice == "4":
join(players) => players
elif choice == "5":
False => running
else:
"Pick a number from 1 to 5." ^0
"Bye." ^0
Python projection (main.py)
import ranking
def trim(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def number(s):
if len(s) == 0 or len(s) > 4:
return -1
n = 0
for c in s:
if not (c >= "0" and c <= "9"):
return -1
n = n * 10 + int(c)
return n
def is_name(s):
if len(s) == 0 or len(s) > 10:
return False
for c in s:
if not (c >= "a" and c <= "z" or c >= "A" and c <= "Z"):
return False
return True
def pad(s, n):
while len(s) < n:
s = s + " "
return s
def lpad(s, n):
while len(s) < n:
s = " " + s
return s
def find(players, name):
for i in range(0, len(players)):
if players[i][0] == name:
return i
return -1
def signed(n):
if n > 0:
return "+" + str(n)
return str(n)
def percent(permille):
whole = 0
r = permille
while r >= 10:
r = r - 10
whole = whole + 1
return str(whole) + "." + str(r) + "%"
def by_points(players):
out = [0] * len(players)
for i in range(0, len(players)):
out[i] = i
for i in range(1, len(out)):
x = out[i]
j = i - 1
while j >= 0 and players[out[j]][1] < players[x][1]:
out[j + 1] = out[j]
j = j - 1
out[j + 1] = x
return out
def standings(players):
pts = [0] * len(players)
for i in range(0, len(players)):
pts[i] = players[i][1]
print("player points rating 1224 1334 1223 1234 fractional")
for i in by_points(players):
print(pad(players[i][0], 12) + lpad(str(players[i][1]), 6) + lpad(str(players[i][2]), 8) + lpad(str(ranking.competition(pts, i)), 7) + lpad(str(ranking.modified(pts, i)), 6) + lpad(str(ranking.dense(pts, i)), 6) + lpad(str(ranking.ordinal(pts, i)), 6) + lpad(ranking.fractional(pts, i), 12))
print("1224 competition: level players share the better rank, and the ranks after them are skipped.")
print("1334 modified competition: level players share the worse rank. 1223 dense: no rank is skipped.")
print("1234 ordinal: level players in the order they joined. Fractional: the average of the places they share.")
def ask_player(players, prompt):
while True:
name = trim(input(prompt))
if name == "":
return -1
i = find(players, name)
if i >= 0:
return i
print("Nobody called " + name + " is in the league.")
def game(players):
a = ask_player(players, "player A> ")
if a == -1:
print("Cancelled.")
return players
while True:
b = ask_player(players, "player B> ")
if b == -1:
print("Cancelled.")
return players
if b != a:
while True:
r = trim(input("result (A, B or draw)> "))
if r == "":
print("Cancelled.")
return players
s2 = -1
if r == "A" or r == "a":
s2 = 2
elif r == "B" or r == "b":
s2 = 0
elif r == "draw":
s2 = 1
if s2 >= 0:
pa = players[a]
pb = players[b]
e = ranking.elo(pa[2], pb[2], s2, 32)
print("Before the game " + pa[0] + " (" + str(pa[2]) + ") was expected to score " + percent(e[0]) + " against " + pb[0] + " (" + str(pb[2]) + ").")
ga = 0
gb = 0
if s2 == 2:
ga = 3
elif s2 == 0:
gb = 3
else:
ga = 1
gb = 1
players[a] = [pa[0], pa[1] + ga, pa[2] + e[1]]
players[b] = [pb[0], pb[1] + gb, pb[2] - e[1]]
print(pa[0] + " " + signed(e[1]) + " to " + str(pa[2] + e[1]) + ", " + pb[0] + " " + signed(0 - e[1]) + " to " + str(pb[2] - e[1]) + ".")
return players
print("Type A, B or draw.")
print("A player cannot play themselves.")
def join(players):
while True:
name = trim(input("name> "))
if name == "":
print("Cancelled.")
return players
if not is_name(name):
print("Type a name of letters, up to 10.")
elif find(players, name) >= 0:
print(name + " is in the league already.")
else:
while True:
s = trim(input("starting rating (100 to 3000)> "))
if s == "":
print("Cancelled.")
return players
r = number(s)
if r >= 100 and r <= 3000:
print(name + " joins on 0 points, rated " + str(r) + ".")
return players + [[name, 0, r]]
print("Type a whole number from 100 to 3000.")
def odds(players):
a = ask_player(players, "player A> ")
if a == -1:
print("Cancelled.")
return
while True:
b = ask_player(players, "player B> ")
if b == -1:
print("Cancelled.")
return
if b != a:
pa = players[a]
pb = players[b]
win = ranking.elo(pa[2], pb[2], 2, 32)
draw = ranking.elo(pa[2], pb[2], 1, 32)
loss = ranking.elo(pa[2], pb[2], 0, 32)
print(pa[0] + " (" + str(pa[2]) + ") is expected to score " + percent(win[0]) + " against " + pb[0] + " (" + str(pb[2]) + ").")
print("For " + pa[0] + ": a win " + signed(win[1]) + ", a draw " + signed(draw[1]) + ", a loss " + signed(loss[1]) + ".")
return
print("A player cannot play themselves.")
players = [["Ana", 9, 1620], ["Ben", 7, 1555], ["Chloe", 7, 1580], ["Dev", 7, 1490], ["Eli", 4, 1450], ["Fay", 4, 1530], ["Gus", 1, 1400]]
print("== Leaderboard ==")
print("A small league: 3 points for a win, 1 each for a draw, and Elo ratings with K = 32.")
running = True
while running:
print("")
print("1) standings 2) record a game 3) what a game is worth 4) new player 5) quit")
choice = trim(input("choice> "))
if choice == "1":
standings(players)
elif choice == "2":
players = game(players)
elif choice == "3":
odds(players)
elif choice == "4":
players = join(players)
elif choice == "5":
running = False
else:
print("Pick a number from 1 to 5.")
print("Bye.")
ranking.eml
eml# P071 leaderboard - ranking with ties five ways, and Elo ratings worked
# out in whole numbers.
def competition(scores, i):
# Standard competition ranking, "1224": 1 + how many scores are higher
# (the corpus case rank-with-ties). Tied players share the better rank
# and the next rank is skipped.
1 => r
for s in scores:
if s > scores[i]:
r + 1 => r
return r
def modified(scores, i):
# Modified competition ranking, "1334": how many scores are at least
# this one, so tied players share the worse rank.
0 => r
for s in scores:
if s >= scores[i]:
r + 1 => r
return r
def dense(scores, i):
# Dense ranking, "1223": 1 + how many DIFFERENT scores are higher.
{} => seen
1 => r
for s in scores:
if s > scores[i] and not s in seen:
True => seen[s]
r + 1 => r
return r
def ordinal(scores, i):
# Ordinal ranking, "1234": everyone a different rank; between equal
# scores, whoever is earlier in the list comes first.
1 => r
for k in [0:len(scores) - 1]:
if scores[k] > scores[i] or (scores[k] == scores[i] and k < i):
r + 1 => r
return r
def fractional(scores, i):
# Fractional ranking, "1 2.5 2.5 4": tied players share the average of
# the ordinal ranks they cover - competition rank c to modified rank m,
# so (c + m) / 2. Returned as text, since it can end in .5.
competition(scores, i) + modified(scores, i) => t
0 => half
t => left
while left >= 2:
left - 2 => left
half + 1 => half
if left == 1:
return str(half) + ".5"
return str(half)
def power(b, e):
# b ** e for whole numbers, by repeated squaring.
1 => out
while e > 0:
if e % 2 == 1:
out * b => out
b * b => b
# e is a few thousand at most, far below 2^53, so the float
# division is exact
int((e - e % 2) / 2) => e
return out
def tenth_power_scaled(r):
# floor(10^(12 + r/400)) for 0 <= r < 400, exactly: the whole number y
# with y^400 <= 10^(4800 + r) < (y + 1)^400, found by halving the range
# 10^12 to 10^13.
power(10, 4800 + r) => target
power(10, 12) => lo
power(10, 13) => hi
while hi - lo > 1:
# lo + hi < 2 x 10^13, below 2^53: halving it as a float is exact
int((lo + hi - (lo + hi) % 2) / 2) => mid
if power(mid, 400) <= target:
mid => lo
else:
mid => hi
return lo
def ten_to(d):
# 10^(d/400) x 10^12 for a whole d >= 0, to 12 digits: d = 400 k + r.
0 => k
d => r
while r >= 400:
r - 400 => r
k + 1 => k
return tenth_power_scaled(r) * power(10, k)
def rounded(n, d):
# n / d to the nearest whole number, halves away from zero (d > 0).
0 => q
if n < 0:
return 0 - rounded(0 - n, d)
2 * n + d => a
2 * d => b
while a >= b:
b => m
1 => k
while m + m <= a:
m + m => m
k + k => k
a - m => a
q + k => q
return q
def elo(ra, rb, score2, k):
# A rated ra plays B rated rb; score2 is A's score doubled (2 win, 1
# draw, 0 loss). A's expected score is E = 1 / (1 + 10^((rb - ra)/400)),
# and A's rating moves by k x (score - E), B's by the opposite.
# Returns [A's expected score in tenths of a percent, A's change].
if rb < ra:
elo(rb, ra, 2 - score2, k) => back
return [1000 - back[0], 0 - back[1]]
power(10, 12) => one
ten_to(rb - ra) => x
# E = one / (one + x); change = k (score2 / 2 - E)
rounded(1000 * one, one + x) => permille
rounded(k * (score2 * (one + x) - 2 * one), 2 * (one + x)) => change
return [permille, change]
Python projection (ranking.py)
def competition(scores, i):
r = 1
for s in scores:
if s > scores[i]:
r = r + 1
return r
def modified(scores, i):
r = 0
for s in scores:
if s >= scores[i]:
r = r + 1
return r
def dense(scores, i):
seen = {}
r = 1
for s in scores:
if s > scores[i] and not s in seen:
seen[s] = True
r = r + 1
return r
def ordinal(scores, i):
r = 1
for k in range(0, len(scores)):
if scores[k] > scores[i] or scores[k] == scores[i] and k < i:
r = r + 1
return r
def fractional(scores, i):
t = competition(scores, i) + modified(scores, i)
half = 0
left = t
while left >= 2:
left = left - 2
half = half + 1
if left == 1:
return str(half) + ".5"
return str(half)
def power(b, e):
out = 1
while e > 0:
if e % 2 == 1:
out = out * b
b = b * b
e = int((e - e % 2) / 2)
return out
def tenth_power_scaled(r):
target = power(10, 4800 + r)
lo = power(10, 12)
hi = power(10, 13)
while hi - lo > 1:
mid = int((lo + hi - (lo + hi) % 2) / 2)
if power(mid, 400) <= target:
lo = mid
else:
hi = mid
return lo
def ten_to(d):
k = 0
r = d
while r >= 400:
r = r - 400
k = k + 1
return tenth_power_scaled(r) * power(10, k)
def rounded(n, d):
q = 0
if n < 0:
return 0 - rounded(0 - n, d)
a = 2 * n + d
b = 2 * d
while a >= b:
m = b
k = 1
while m + m <= a:
m = m + m
k = k + k
a = a - m
q = q + k
return q
def elo(ra, rb, score2, k):
if rb < ra:
back = elo(rb, ra, 2 - score2, k)
return [1000 - back[0], 0 - back[1]]
one = power(10, 12)
x = ten_to(rb - ra)
permille = rounded(1000 * one, one + x)
change = rounded(k * (score2 * (one + x) - 2 * one), 2 * (one + x))
return [permille, change]