rnumeral-system

How can I convert between numeral systems in R?


I see that there is a built-in function for the...rather abstract...use of transforming from the a decimal representation to roman numerals (Convert roman numerals to numbers in R), but I'm not able to find a built-in way to convert from decimal to other, similar, systems, like base 3 or base 11. I see that binary and hex (base 16) are well supported, but is there a package for converting to/from an arbitrary positional numeral system.

I could make such a package, but I suspect it already exists and my google-fu is just falling short?


Solution

  • You could write your own S3 class:

    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")
    }
    

    Which will need a format and print method:

    format.base <- function(b, ...) 
    {
      sapply(b, function(x) 
        {
          glyphs <- c(0:9, LETTERS)
          base   <- attr(x, "base")
          vec    <- attr(x, "representation")
          paste0(glyphs[vec + 1], collapse = "")
        })
    }
    
    print.base <- function(b, ...) print(format(b), quote = FALSE)
    

    We also need to make sure that maths operations work properly:

    Ops.base <- function(e1, e2) {
      base <- attr(e1[[1]], "base")
      e1   <- unlist(e1)
      e2   <- unlist(e2)
      base(NextMethod(.Generic), base)
    }
    
    Math.base <- function(e1, e2) {
      base <- attr(e1[[1]], "base")
      e1   <- unlist(e1)
      e2   <- unlist(e2)
      base(NextMethod(.Generic), base)
    }
    

    And if you want to use it inside a data frame you need an as.data.frame method:

    as.data.frame.base <- function(b, ...) 
    {
      structure(list(b),  
                class = "data.frame", 
                row.names = seq_along(b))
    }
    

    Which all allows the following behaviour:

    data.frame(binary = base(1:20, 2), hex = base(1:20, 16), oct = base(1:20, 8))
    #>    binary hex oct
    #> 1       1   1   1
    #> 2      10   2   2
    #> 3      11   3   3
    #> 4     100   4   4
    #> 5     101   5   5
    #> 6     110   6   6
    #> 7     111   7   7
    #> 8    1000   8  10
    #> 9    1001   9  11
    #> 10   1010   A  12
    #> 11   1011   B  13
    #> 12   1100   C  14
    #> 13   1101   D  15
    #> 14   1110   E  16
    #> 15   1111   F  17
    #> 16  10000  10  20
    #> 17  10001  11  21
    #> 18  10010  12  22
    #> 19  10011  13  23
    #> 20  10100  14  24
    

    And:

    x <- base(67, 11)
    y <- base(35, 2)
    x + y
    #> [1] 93
    
    base(x + y, 10)
    #> [1] 102