Reputation: 5907
Recently, I was reading about the Ancient Babylonian Civilization that used a number system with base 60 instead of base 10. Even with this number system at base 60, they were still able to approximate the square root of 2 — and that too, thousands of years ago!
I was curious about this, and wanted to see how numbers from our decimal system (base 10) can be converted into the sexagesimal system (base 60). Using the R programming language, I found this link in which an answer is provided on converting numbers from some base to a different base.
However, it seems here that the base can only be between 2 and 36 (I want base 60):
base <- function(b, base = 10)
{
base <- as.integer(base)
if(base > 36 | base < 2) stop("'base' must be between 2 and 36.")
structure(lapply(b, function(x)
{
n <- ceiling(log(x, base))
vec <- numeric()
val <- x
while(n >= 0)
{
rem <- val %/% base^n
val <- val - rem * base^n
vec <- c(vec, rem)
n <- n - 1
}
while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
structure(x, base = base, representation = vec)
}), class = "base")
}
The article that I linked to reads in the headline "One eighth equals seven and thirty in this strange base 60 world" - I would like to see this and convert "1/8" from the decimal system into "7 and 30" in the sexagesimal system.
Can someone please help me with this?
Upvotes: 8
Views: 1790
Reputation: 11306
Here is a vectorized version of @BenBolker's base
function, which I've called vbase
. It eliminates the loop over the n
elements of the supplied numeric vector x
, which can be quite costly in R even for modest n
. It happens to be more general, as it supports both positive and nonpositive x
.
vbase
returns a matrix with 1+digits
rows (one for a sign bit) and n
columns. The matrix result is nice on the eyes and is convenient for pattern-finding.
vbase <- function(x, base = 10, digits = 6) {
stopifnot(is.numeric(x), is.finite(x),
is.numeric(base), length(base) == 1L, base >= 2,
is.numeric(digits), length(digits) == 1L, digits >= 1)
n <- length(x)
if (n == 0L) {
return(NULL)
}
base <- as.integer(base)
digits <- as.integer(digits)
m <- 1L + digits
nz <- x != 0
res <- matrix(0L, m, n)
if (any(nz)) {
nz <- which(nz)
y <- abs(x[nz])
e <- ceiling(log(max(y), base))
pow <- base^e
res[1L, ] <- sign(x)
i <- 2L
while (i <= m) {
quo <- y %/% pow
res[i, nz] <- quo
y <- y - quo * pow
pow <- pow / base
i <- i + 1L
}
} else {
e <- 0
}
ech <- sprintf("%d", seq.int(e, by = -1, length.out = digits))
dimnames(res) <- list(EXPONENT = c("sign", ech), ELEMENT = names(x))
attr(res, "x") <- x
attr(res, "base") <- base
attr(res, "digits") <- digits
res
}
x <- 60^(-2:2)
vbase(c(-x, x), base = 60, digits = 5)
ELEMENT
EXPONENT [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
sign -1 -1 -1 -1 -1 1 1 1 1 1
2 0 0 0 0 1 0 0 0 0 1
1 0 0 0 1 0 0 0 0 1 0
0 0 0 1 0 0 0 0 1 0 0
-1 0 1 0 0 0 0 1 0 0 0
-2 1 0 0 0 0 1 0 0 0 0
attr(,"x")
[1] -2.777778e-04 -1.666667e-02 -1.000000e+00 -6.000000e+01 -3.600000e+03
[6] 2.777778e-04 1.666667e-02 1.000000e+00 6.000000e+01 3.600000e+03
attr(,"base")
[1] 60
attr(,"digits")
[1] 5
vbase(1 / 8, base = 60, digits = 10)
ELEMENT
EXPONENT [,1]
sign 1
0 0
-1 7
-2 29
-3 59
-4 59
-5 59
-6 59
-7 59
-8 59
-9 59
attr(,"x")
[1] 0.125
attr(,"base")
[1] 60
attr(,"digits")
[1] 10
One limitation of the matrix result is that the greatest element of x
(in absolute value) determines what sequence of exponents is used to represent all of the other elements. For example:
vbase(2^c(-10, 1), base = 2, digits = 2)
ELEMENT
EXPONENT [,1] [,2]
sign 1 1
1 0 1
0 0 0
attr(,"x")
[1] 0.0009765625 2.0000000000
attr(,"base")
[1] 2
attr(,"digits")
[1] 2
Here, 2^(-10)
is misrepresented because the exponents count down from 1. You could increase digits
to 12 to capture both 2^(-10)
and 2^1
, but doing so would introduce many zeros into the result, which isn't an optimal use of memory. That said, if length(x)
isn't huge—and certainly if base = 60
—then you shouldn't have to worry much about running out of memory.
Upvotes: 3
Reputation: 101335
Perhaps we can try the code below (given x < 1
)
f <- function(x, base = 60, digits = 6) {
if (digits == 1) {
return(round(x))
}
p <- x * base
c(p %/% 1, Recall(p %% 1, digits = digits - 1))
}
which gives
> f(1 / 8)
[1] 7 30 0 0 0 0
> f(1 / 7)
[1] 8 34 17 8 34 0
> f(1/3)
[1] 20 0 0 0 0 0
Upvotes: 3
Reputation: 226182
The code as given almost works. The limitation to bases < 36 is only there because the original author wanted to express the values with the symbols [0-9A-Z]. Removing that limitation and extending the algorithm to allow extra digits 'after the decimal point' (or 'after the sexagesimal point' in the case of base 60 :-) ) we get something that almost works (function definition below):
base(1/8, base = 60, digits = 6)
[[1]]
[1] 0.125
attr(,"base")
[1] 60
attr(,"representation")
[1] 7 29 59 59 59 59
attr(,"class")
[1] "base"
Instead of "7 30" we get "7 29 (59 repeating)", which is analogous to doing a decimal calculation that should be 0.2 and instead getting 0.1999....
This would presumably be fixable with an appropriate 'numeric fuzz' threshold.
The other thing that's missing from the code, now that it does fractional parts, is that the result should return information that tells you where the 'decimal' point is located (at the simplest, including the value of digits
in the output).
There are other aspects of the code that could be improved (e.g. pre-allocating vec
rather than building it up iteratively).
base <- function(b, base = 10, digits = 0) {
base <- as.integer(base)
structure(lapply(b, function(x)
{
n <- ceiling(log(x, base))
vec <- numeric()
val <- x
while(n >= -1*digits ) {
rem <- val %/% base^n
val <- val - rem * base^n
vec <- c(vec, rem)
n <- n - 1
}
while(vec[1] == 0 & length(vec) > 1) vec <- vec[-1]
structure(x, base = base, representation = vec)
}), class = "base")
}
Upvotes: 11