Recently, I was reading about the Ancient Babylonian Civilization that used a number system with base 60 instead of base 10 (https://blogs.scientificamerican.com/roots-of-unity/the-joy-of-sexagesimal-floating-point-arithmetic/). 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?
CodePudding user response:
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")
}
CodePudding user response:
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
CodePudding user response:
I've attempted to vectorize @BenBolker's answer to avoid a loop over the n elements of the supplied numeric vector x.
The function vbase returns a matrix with n columns and one row for each retained "bit". I've not given much thought to dealing properly with floating point, but vbase at least scales nicely with n and supports nonpositive x. The matrix result makes pattern-finding a bit easier, too...
vbase <- function(x, base = 10, digits = 0) {
stopifnot(is.numeric(x), is.finite(x),
is.numeric(base), length(base) == 1L, base >= 2,
is.numeric(digits), length(digits) == 1L, digits >= 0)
n <- length(x)
if (n == 0L) {
return(NULL)
}
## Get number of digits to left of decimal
y <- abs(x)
base <- as.integer(base)
width <- as.integer(floor(1 log(max(y, 1), base)))
## Number of "bits"
m <- 1L width as.integer(digits)
## Initialize result
res <- matrix(0L, m, n)
res[1L, ] <- sign(x)
## Loop over digits
pos <- 2L
pow <- base^(width - 1L)
while (pos <= m) {
quo <- y %/% pow
res[pos, ] <- quo
y <- y - quo * pow
pos <- pos 1L
pow <- pow / base
}
## Finish up
dimnames(res) <- list(BIT = c("sign", (width - 1L):0, -seq_len(digits)), ELT = names(x))
attr(res, "x") <- x
attr(res, "base") <- base
attr(res, "digits") <- digits
class(res) <- "base"
res
}
x <- 60^(-2:2)
vbase(c(-x, x), base = 60, digits = 2)
ELT
BIT [,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 2.777778e-04 1.666667e-02 1.000000e 00 6.000000e 01 3.600000e 03
attr(,"base")
[1] 60
attr(,"digits")
[1] 2
attr(,"class")
[1] "base"
vbase(1 / 8, base = 60, digits = 6)
ELT
BIT [,1]
sign 1
0 0
-1 7
-2 29
-3 59
-4 59
-5 59
-6 59
attr(,"x")
[1] 0.125
attr(,"base")
[1] 60
attr(,"digits")
[1] 6
attr(,"class")
[1] "base"
