Converting Numbers from Base 10 to Base 60
Asked Answered
H

3

8

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!

enter image description here

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?

Huberty answered 30/1, 2022 at 19:44 Comment(3)
gtools::baseOf might be of use (though that assumes integers, trickery might be needed)Pasteurization
for what it's worth, with the Unix built-in calculator bc, "echo "obase = 60; 1/8" | bc -l" gives .07 30 00 00 00 00 00 00 00 00 00 00. I have used this in the past because I'm too lazy to implement arbitrary-precision base transforms, e.g. see rpubs.com/bbolker/4113Gujarat
To do this manually : math.stackexchange.com/questions/1665591/…Warder
G
11

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")
}
Gujarat answered 30/1, 2022 at 20:34 Comment(0)
M
3

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
Mella answered 30/1, 2022 at 22:12 Comment(0)
H
3

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.

Housewife answered 30/1, 2022 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.