R: 将文本进行序列化、Base64编码/解码后发现结果不完全匹配

3
在我关于使用serialize()创建对象CSV的之前的问题中,jmoy给出了一个很好的答案,他建议对序列化文本进行base64编码。这正是我想要的。但奇怪的是,当我尝试实践时,得到的结果看起来是正确的,但却不完全匹配我运行过的序列化/编码过程。
下面的示例将包含3个向量的列表进行序列化。然后对每个向量进行base64编码,并将其与键一起写入文本文件。键只是向量的索引号。然后我反向执行该过程,并从CSV中读取每行。最后你可以看到有些项并没有完全匹配。这是否是浮点数问题?还是其他原因?
require(caTools)

randList <- NULL
set.seed(2)

randList[[1]] <- rnorm(100)
randList[[2]] <- rnorm(200)
randList[[3]] <- rnorm(300)

#delete file contents
fileName <- "/tmp/tmp.txt"
cat("", file=fileName, append=F)

i <- 1
for (item in randList) {
  myLine <- paste(i, ",", base64encode(serialize(item, NULL, ascii=T)), "\n", sep="")
  cat(myLine, file=fileName, append=T) 
  i <- i+1
}

linesIn <- readLines(fileName, n=-1)

parsedThing <- NULL
i <- 1
for (line in linesIn){
  parsedThing[[i]] <- unserialize(base64decode(strsplit(linesIn[[i]], split=",")[[1]][[2]], "raw"))
  i <- i+1
  }

#floating point issue?
identical(randList, parsedThing)

for (i in 1:length(randList[[1]])) {
  print(randList[[1]][[i]] == parsedThing[[1]][[i]])
}

i<-3
randList[[1]][[i]] == parsedThing[[1]][[i]]

randList[[1]][[i]]
parsedThing[[1]][[i]]

以下是简化版输出:
> #floating point issue?
> identical(randList, parsedThing)
[1] FALSE
> 
> for (i in 1:length(randList[[1]])) {
+   print(randList[[1]][[i]] == parsedThing[[1]][[i]])
+ }
[1] TRUE
[1] TRUE
[1] FALSE
[1] FALSE
[1] TRUE
[1] FALSE
[1] TRUE
[1] TRUE
[1] FALSE
[1] FALSE
...
> 
> i<-3
> randList[[1]][[i]] == parsedThing[[1]][[i]]
[1] FALSE
> 
> randList[[1]][[i]]
[1] 1.587845
> parsedThing[[1]][[i]]
[1] 1.587845
> 

实际上,我们看不到你的输出,因为你的输出有点过于简洁。 - Dirk Eddelbuettel
3个回答

2

JD: 我在我的Linux机器上运行了你的代码片段,然后查看了randList[[1]][[i]] - parsedThing[[1]][[i]]计算出的差异。

是的,这些值是不同的,但只在我的机器浮点数容差范围内。一个典型的差异是-4.440892e-16,这非常小。一些差异为零。

我并不惊讶保存/恢复引入了(微小的)变化。任何重要的数据转换都有可能“翻转”最不重要的数字。


欢迎加入 SO,Paul。很高兴你能加入我们的团队。 - Shane
这正是让我认为这是浮点舍入类型错误的原因。尽管非常小,但噪声的引入令我有些惊讶。这超出了我的经验范围,所以我想询问一下原因。我在我的代码中继续前进,假设这已经足够“接近”。很高兴你在 Stack Overflow 上! - JD Long

2

好的,既然你展示了输出结果,我可以向你解释一下你正在做什么(在这里遵循Paul的领导)。

由于这是已知问题(请参见例如此R FAQ条目),因此您应该使用以下任何一种:

  • identical()
  • all.equal()
  • checkEquals等来自RUnit包的函数

总之,你使用的base64编码没有问题。 你只是使用了错误的“完全相同”的定义。 但是嘿,我们是经济学家,任何低于万亿或两万亿的东西都是舍入误差...


我曾在一个会计公司工作过一段时间,所以当我无法完全复制某些东西时,我的耳道内会有一些不安的感觉。谢谢你给我做了一个理智的检查。 - JD Long
请查看(经典)计算机科学家应该了解的浮点运算知识。虽然你我不是计算机科学家,但我们偶尔在电视上扮演这个角色。 - Dirk Eddelbuettel
我之前确实读过那个的大部分内容。恐怕那些想法已经被啤酒所取代了。我想责怪我的孩子抹去了我的记忆,但这可能不现实。是啤酒的原因。 - JD Long

2
您在调用serialize时使用的ascii=T会导致R进行不精确的二进制-十进制-二进制转换,从而导致值的差异。如果您删除ascii=T,则会得到与现在完全相同的数字,因为它是一个二进制表示形式。 base64encode可以对原始向量进行编码,因此不需要ascii=Tserialize使用的二进制表示形式是与体系结构无关的,因此您可以在一台计算机上进行序列化,然后在另一台计算机上进行反序列化。
参考:http://cran.r-project.org/doc/manuals/R-ints.html#Serialization-Formats

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