# convert x to its unsigned base-n representation
# n: the base
# ndigits: the maximum number of digits
= function(x, n, ndigits) {
base_n = numeric(ndigits)
r if (x >= n ^ ndigits)
stop("x is out of range")
= 1
i while (x > 0) {
= floor(x / n)
d = x %% n
r[i] = d
x = i + 1
i
}
rev(r)
}
# convert from unsigned base-n back to decimal
= function(r, n) {
to_decimal = rev(r) # put the lowest digit to the leftmost
r = length(r)
ndigits = 0
x for (i in 1:ndigits)
= x + r[i] * n ^ (i - 1)
x
x }
Recently on Puzzle Corner, we looked at the Four Weights problem: what are the four weights that can weigh any object
The puzzle just asks you to find the values of the weights, which are
This isn’t a practical question, since if I know
Converting to a base-n representation
Assume you want to represent a nonnegative integer with up to
i = 1 # initialize the index
r = zeros(m) # all-zeros vector of length m
if x >= 2^m then throw("x is out of range")
while x > 0
d = floor(x/2)
r[i] = x mod 2
x = d
i = i+1
return reverse(r) # so lowest value digit is rightmost
Let’s do it by hand for
13/2 = 6 rem 1 (d = 6, r[1] = 1)
6/2 = 3 rem 0
3/2 = 1 rem 1
1/2 = 0 rem 1
So we have 13 = 1101 in base-2, or (1*8) + (1*4) + (0*2) + (1*1)
.
For an unsigned trinary representation, the idea is the same, only you divide by 3 instead of 2. Let’s try it with 18, using 4 digits (which can represent up to the value
18/3 = 6 rem 0
6/3 = 2 rem 0
2/3 = 0 rem 2
So 18 = 0200 in base-3, or (0*27) + (2*9) + (0*3) + (0*1)
.
We can write a general function to convert a decimal number x
to base-n representation.
# convert 13 to binary
= base_n(13, 2, 4)
r # confirm this is 13
stopifnot(to_decimal(r, 2) == 13)
r
[1] 1 1 0 1
# convert 18 to trinary
= base_n(18, 3, 4)
r # confirm this is 18
stopifnot(to_decimal(r, 3) == 18)
r
[1] 0 2 0 0
Converting to signed trinary
Recall that when solving the Four Weights problem, we established that any nonnegative integer value
where the weights (1, 3, 9, 27)
—we write it smallest-first, consistently with the previous post on this problem—and
The four digits still represent
To modify the unsigned trinary conversion to a signed one, we use the observation that
- The equation
x/3 = d rem 2
is equivalent tox/3 = (d+1) rem -1
.
This is a bit of an abuse of the notation; but here’s an example of what we are trying to say. We know that
8/3 = 2 rem 2
, which is the same as saying8 = 2*3 + 2
.
Another way of writing this is
8 = (2+1)*3 - 1 = 3*3 - 1
which we’ll write as:8/3 = 3 rem -1
So (when considering only nonnegative
i = 1
r = zeros(m)
maxval = (3^m - 1)/2
if x > maxval then throw("x is out of range")
while x > 0
d = floor(x/3)
r[i] = x mod 3
if r[i]==2
d = d+1
r[i] = -1
x = d
i = i+1
return r # we won't reverse it, to be consistent with our puzzle solution
Let’s convert 18 to signed trinary.
18/3 = 6 rem 0
6/3 = 2 rem 0
2/3 = 0 rem 2 = 1 rem -1
1/3 = 0 rem 1
So 18 codes to [0 0 -1 1] = (0*1) + (0*3) + (-1*9) + (1*27)
. In the scale notation that we used while solving the puzzle, we would write this as
Here’s the code.
# convert nonnegative x to signed trinary
= function(x) {
weigh if (x > 40)
stop("x out of range")
= numeric(4)
r = 1
i while (x > 0) {
= floor(x / 3)
d = x %% 3
r[i] if (r[i] == 2) {
= d + 1
d = -1
r[i]
}= d
x = i + 1
i
}
r
}
# write the signed trinary representation in our scale notation
= function(signs) {
scale_notation = c(1, 3, 9, 27)
w = which(signs > 0)
lefti = which(signs < 0)
righti
= paste(w[lefti], collapse = ", ")
leftset if (length(righti) > 0)
= paste("x,", paste(w[righti], collapse = ", "))
rightset else
= "x"
rightset
= paste("[ {", leftset, "} | {", rightset, "} ]")
notation
notation
}
# convert signed trinary back to decimal
= function(signs) {
to_x = c(1, 3, 9, 27)
w = sum(w * signs) # dot product of w and signs
x }
Let’s try a few.
= 18
x = weigh(x)
signs # convert back and check it's the same number
stopifnot(x == to_x(signs))
scale_notation(signs)
[1] "[ { 27 } | { x, 9 } ]"
= 35
x = weigh(x)
signs stopifnot(x == to_x(signs))
scale_notation(signs)
[1] "[ { 9, 27 } | { x, 1 } ]"
= 15
x = weigh(x)
signs stopifnot(x == to_x(signs))
scale_notation(signs)
[1] "[ { 27 } | { x, 3, 9 } ]"
= 30
x = weigh(x)
signs stopifnot(x == to_x(signs))
scale_notation(signs)
[1] "[ { 3, 27 } | { x } ]"
= 4
x = weigh(x)
signs stopifnot(x == to_x(signs))
scale_notation(signs)
[1] "[ { 1, 3 } | { x } ]"
So now we can weigh objects we know the weight of. Hurrah? But I still think it’s cute.
Nina Zumel is a data scientist based in San Francisco, with 20+ years of experience in machine learning, statistics, and analytics. She is the co-founder of the data science consulting firm Win-Vector LLC, and (with John Mount) the co-author of Practical Data Science with R, now in its second edition.