在R中将十进制转换为二进制?

43

在R中,将数字转换为二进制(以字符串形式表示,例如5将转换为"0000000000000101")的最简单方法是什么?虽然有intToBits函数可以用,但它返回的是一个字符串向量,而非一个字符串:

> intToBits(12)
 [1] 00 00 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[26] 00 00 00 00 00 00 00

我尝试了其他一些函数,但没有成功:

> toString(intToBits(12))
[1] "00, 00, 01, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00"

1
intToBits 不会返回一个字符串向量,而是返回一个原始向量。请注意,该向量有32个元素。这是因为R使用32位整数,所以每个位都有一个元素。我无法想象出一种情况,将一个数字表示为字面上的位字符串会有什么用处...你到底想做什么呢? - Joshua Ulrich
我正在研究密码分析的一些示例,能够展示密钥为位序列“011010110”等是非常好的。 - Jay
2
@DWin:在Debian中,它实际上被列为“GNU R统计计算和图形系统”,项目页面也称其为GNU项目,这就是我称其为GNU R的原因。并不是因为我对这些事情挑剔,而是习惯了说“GNU R”以帮助消除歧义(进行Google搜索“R”并不真正有用)。 - Jay
6
R核心小组不喜欢将它称为GNU R,因为他们是作者,我认为他们最终有决定权。在互联网上搜索GNU R会错过大部分内容。请使用术语"r-project",或使用RSiteSearch()或rseek作为搜索引擎。有些人报告使用"r:language"作为Google术语成功。 - IRTFM
1
@42- 真不幸。如果这让核心作者感到烦恼,他们就不应该将其列为GNU项目。然而,他们确实这样做了,并且在官方网站上继续这样做。 - Konrad Rudolph
11个回答

29

paste(rev(as.integer(intToBits(12))), collapse="") 可以完成这个任务。

paste 函数的 collapse 参数可以将向量合并为字符串。不过,您必须使用 rev 函数来获取正确的字节顺序。

as.integer 函数可以移除多余的零。


26
请注意,intToBits()返回的是“原始”向量,而不是字符向量(字符串)。请注意,我的答案是@nico原始答案的轻微扩展,它删除了每个位数前面的“0”。
paste(sapply(strsplit(paste(rev(intToBits(12))),""),`[[`,2),collapse="")
[1] "00000000000000000000000000001100"

为了更清晰明了地分解步骤,请按如下格式:
# bit pattern for the 32-bit integer '12'
x <- intToBits(12)
# reverse so smallest bit is first (little endian)
x <- rev(x)
# convert to character
x <- as.character(x)
# Extract only the second element (remove leading "0" from each bit)
x <- sapply(strsplit(x, "", fixed = TRUE), `[`, 2)
# Concatenate all bits into one string
x <- paste(x, collapse = "")
x
# [1] "00000000000000000000000000001100"

或者,如@nico展示的那样,我们可以使用as.integer()作为更简洁的方法来去除每个位数前导零。
x <- rev(intToBits(12))
x <- paste(as.integer(x), collapse = "")
# [1] "00000000000000000000000000001100"

为了方便复制粘贴,这里给出了上述内容的函数版本:

dec2bin <- function(x) paste(as.integer(rev(intToBits(x))), collapse = "")

@bubakazouba:在这个例子中,12是一个32位整数。你为什么认为它有太多位呢?有什么需要修复的吗? - Joshua Ulrich
对不起,我的意思是对于我所需的内容来说,这是很多位元,我并不是指需要“修复”什么东西。我只是想知道是否有一种简单的方法来改变位数? - bubakazouba
@bubakazouba:简而言之,不行。基本的R只支持32位整数。如果你知道这个数字可以用更少的位数来表示(例如一个字节或短整型),你可以使用substr函数提取最右边的X位。但是你真正应该使用readBinwriteBin来处理二进制数据。 - Joshua Ulrich

21

我认为你可以使用R.utils包,然后使用intToBin()函数。

>library(R.utils)

>intToBin(12)
[1] "1100"

> typeof(intToBin(12))
[1] "character"

16

intToBits的最大限制为2^32,但如果我们想将1e10转换为二进制怎么办?这里是一个将浮点数转换为二进制的函数,假设它们是被存储为numeric的大整数。

dec2bin <- function(fnum) {
  bin_vect <- rep(0, 1 + floor(log(fnum, 2)))
  while (fnum >= 2) {
    pow <- floor(log(fnum, 2))
    bin_vect[1 + pow] <- 1
    fnum <- fnum - 2^pow
  } # while
  bin_vect[1] <- fnum %% 2
  paste(rev(bin_vect), collapse = "")
} #dec2bin

这个函数在2^53 = 9.007199e15之后开始失去精度,但对于较小的数字可以正常工作。

microbenchmark(dec2bin(1e10+111))
# Unit: microseconds
#                 expr     min       lq     mean   median      uq    max neval
# dec2bin(1e+10 + 111) 123.417 125.2335 129.0902 126.0415 126.893 285.64   100
dec2bin(9e15)
# [1] "11111111110010111001111001010111110101000000000000000"
dec2bin(9e15 + 1)
# [1] "11111111110010111001111001010111110101000000000000001"
dec2bin(9.1e15 + 1)
# [1] "100000010101000110011011011011011101001100000000000000"

我不需要转换这么大的数字,但无论如何都是很好的答案!+1 - Jay
2
我遇到了一个问题,需要处理大数字,在stackoverflow上搜索解决方案后,最终编写了自己的代码 :) - inscaven
我点赞了这个回答,因为它涵盖了以数字形式存储的大整数情况。我很高兴看到inscaven的回答也将涵盖小数情况:dec2bin(0.3) # Error in rep(0, 1 + floor(log(fnum, 2))) : invalid 'times' argument. 另外,请注意 dec2bin(0) # Error in rep(0, 1 + floor(log(fnum, 2))) : invalid 'times' argument. 因此,必须正确处理0的情况。 - Erdogan CEVHER
@inscaven,你是否也实现了针对大型“bin”字符串的反向bin2dec? - mshaffer
如果您有一个二进制字符串bs,可以使用此单行代码:sum(2^(nchar(bs) - stringi::stri_locate_all(bs, fixed = "1")[[1]][,1]))。请注意,此代码可以正确处理长度不超过53个字符的二进制字符串。 - inscaven

6

如果您启用了bit64包并且遇到了64位整数,那该怎么办呢?除了@epwalsh的答案外,所有给出的答案都无法操作64位整数,因为R和R.utils的基于C的内部不支持它。如果您首先加载bit64包,@epwalsh的解决方案在R中运行良好,但使用循环在R中速度非常缓慢(所有速度相对而言)。

o.dectobin <- function(y) {
  # find the binary sequence corresponding to the decimal number 'y'
  stopifnot(length(y) == 1, mode(y) == 'numeric')
  q1 <- (y / 2) %/% 1
  r <- y - q1 * 2
  res = c(r)
  while (q1 >= 1) {
    q2 <- (q1 / 2) %/% 1
    r <- q1 - q2 * 2
    q1 <- q2
    res = c(r, res)
  }
  return(res)
}

dat <- sort(sample(0:.Machine$integer.max,1000000))
system.time({sapply(dat,o.dectobin)})
#   user  system elapsed 
# 61.255   0.076  61.256 

如果我们对它进行字节编译,就能让它变得更好...
library(compiler)
c.dectobin <- cmpfun(o.dectobin)
system.time({sapply(dat,c.dectobin)})
#   user  system elapsed 
# 38.260   0.010  38.222 

...但是它仍然相当慢。如果我们用C语言编写自己的内部机制(这正是我在这里所做的,借鉴了@epwalsh的代码 - 我显然不是一名C程序员),我们可以得到大幅度加速。

library(Rcpp)
library(inline)
library(compiler)
intToBin64.worker <- cxxfunction( signature(x = "string") , '    
#include <string>
#include <iostream>
#include <sstream>
#include <algorithm>
// Convert the string to an integer
std::stringstream ssin(as<std::string>(x));
long y;
ssin >> y;

// Prep output string
std::stringstream ssout;


// Do some math
int64_t q2;
int64_t q1 = (y / 2) / 1;
int64_t r = y - q1 * 2;
ssout << r;
while (q1 >= 1) {
q2 = (q1 / 2) / 1;
r = q1 - q2 * 2;
q1 = q2;
ssout << r;
}


// Finalize string
//ssout << r;
//ssout << q1;
std::string str = ssout.str();
std::reverse(str.begin(), str.end());
return wrap(str);
', plugin = "Rcpp" )

system.time(sapply(as.character(dat),intToBin64.worker))
#   user  system elapsed 
#  7.166   0.010   7.168 

```


6
我现在注意到完全荒谬的是,bit64有一个as.bitstring函数,比我的Rcpp函数快两倍...但我会把这个留在这里作为愚蠢的纪念和潜在的提醒,如何从integer64桥接到C++并返回...但如果需要更有效的方法来做到这一点,一定要查看bit64源代码。 - russellpierce
1
你的"愚蠢纪念碑"评论让我想起了这个网站: https://despair.com/products/mistakes。 - Joshua Ulrich
我在想,是否只需重新调整内部的“intToBits”以处理更宽的输入就可以了呢?https://github.com/wch/r-source/blob/21ac5ee817a45d98361da324285c77e2f9c4f73d/src/main/raw.c#L124-L142 - MichaelChirico
@MichaelChirico 若我没记错的话,bit64 库会在底层用两个 double 实现 64 位整数。所以,仅通过改变向量和循环边界来使一切运行顺畅,我会有些意外。此外,看到这个排名低下的答案三年后还有评论也让我感到有些惊奇。=) - russellpierce
2
bit64将integer64实现为一个双精度浮点数——REALSXP。一个双精度浮点数和一个64位整数都是64位。它们占用相同的内存,但内容的表示方式不同。我的评论是因为我在这个页面上回应@MichaelChirico的评论/编辑我的答案。我碰巧看到了你的评论,让我微笑并想起了那个链接。 - Joshua Ulrich

6

5

该函数将接受十进制数并返回相应的二进制序列,即由1和0组成的向量。

dectobin <- function(y) {
  # find the binary sequence corresponding to the decimal number 'y'
  stopifnot(length(y) == 1, mode(y) == 'numeric')
  q1 <- (y / 2) %/% 1
  r <- y - q1 * 2
  res = c(r)
  while (q1 >= 1) {
    q2 <- (q1 / 2) %/% 1
    r <- q1 - q2 * 2
    q1 <- q2
    res = c(r, res)
  }
  return(res)
}

我认为写成 y %/% 2 更好。 - skan

2
尝试使用»binaryLogic«
library(binaryLogic)

ultimate_question_of_life_the_universe_and_everything <- as.binary(42)

summary(ultimate_question_of_life_the_universe_and_everything)
#>   Signedness  Endianess value<0 Size[bit] Base10
#> 1   unsigned Big-Endian   FALSE         6     42

> as.binary(0:3, n=2)
[[1]]
[1] 0 0

[[2]]
[1] 0 1

[[3]]
[1] 1 0

[[4]]
[1] 1 1

CRAN上没有这样的库。 - Carl Witthoft

1

--最初作为对@JoshuaUlrich的答案的编辑添加,因为它完全是他和@nico的推论;他建议我添加一个单独的答案,因为它引入了他不熟悉的软件包--

由于@JoshuaUlrich的答案功能非常强大(6个连续函数),我发现magrittr/tidyverse的管道运算符(%>%)使得以下解决方案更加优雅:

library(magrittr)

intToBits(12) %>% rev %>% as.integer %>% paste(collapse = '')
# [1] "00000000000000000000000000001100"

我们还可以添加一个最终的as.integer调用来截断所有这些前导零:
intToBits(12) %>% rev %>% as.integer %>% paste(collapse = '') %>% as.integer
# [1] 1100

请注意,这再次存储为integer,这意味着R将其视为以10为基数表示的1100,而不是以2为基数表示的12。
值得一提的是,@ramanudle(和其他人,特别是@russellpierce,他给出了C++实现)的方法通常是低级语言中建议的标准方法,因为它是相当有效的方法(它适用于任何可以存储在R中的数字,即不限于integer范围)。
还值得一提的是,intToBits的C实现非常简单--有关可能对仅限于R的用户不熟悉的部分,请参见https://en.wikipedia.org/wiki/Bitwise_operations_in_CC实现

0
这里有一个递归函数,可以将正整数转换为2到9进制的任意进制。该函数通过重复除以基数并将商转换为目标进制来工作,通过调用自身。答案的数字是沿途每个除法的余数。
convertBase <- function(x, base=2L, g="") {
  if (x < 1) return(g)
  convertBase( x %/% base, base, paste0(x %% base, g) )
}

例如,convertBase(545,6) 将首先将 545 除以 6,得到商为 90,余数为 5。因此,“5” 是最右边的数字,然后函数调用 convertBase(90,6,"5"),它将 90 除以 6 得到商为 15,余数为 0。因此“0”是下一个数字(向左移动),函数调用 convertBase(15,0,"05"),它将 15 除以 6 得到商为 2,余数为 3,所以下一个数字(再次向左移动)是“3”,最后是“2”,返回“2305”。默认基数为 2(二进制);例如 convertBase(12) 返回“1100”。
如果 x 为 0(或负数),函数返回“”。如果 x 不是整数,则函数无法工作。如果您需要转换为大于 10 的基数,则我提供的函数将无法工作,但很容易进行适应。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接