如何在R中返回小数位数

27

我正在使用R。 我有一系列十进制度数坐标,我希望按照这些数字具有的小数位数对这些坐标进行排序(即,我将要丢弃具有太少小数位数的坐标)。
在R中是否有一个函数可以返回数字的小数位数,以便我能够将其纳入函数编写中?
输入示例:

AniSom4     -17.23300000        -65.81700

AniSom5     -18.15000000        -63.86700

AniSom6       1.42444444        -75.86972

AniSom7       2.41700000        -76.81700

AniLac9       8.6000000        -71.15000

AniLac5      -0.4000000        -78.00000

我希望编写一个脚本,丢弃AniLac9和AniLac 5,因为这些坐标记录的精度不够。我想要丢弃那些经度和纬度都少于3个非零小数值的坐标。


2
你的数据文件中如何记录小数位?例如,它是否同时包含34.4和34.400,并且这两者是否被视为不同?提供示例输入和期望输出将会很有帮助。 - Aaron left Stack Overflow
2
警告:在x86和其他主流处理器中,分数小数数值无法被准确表示。除非以文本形式进行处理,否则将得到虚假的结果。 - Alex Brown
14个回答

49
你可以轻松编写一个小函数来完成这个任务,例如:

decimalplaces <- function(x) {
    if ((x %% 1) != 0) {
        nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed=TRUE)[[1]][[2]])
    } else {
        return(0)
    }
}

并运行:

> decimalplaces(23.43234525)
[1] 8
> decimalplaces(334.3410000000000000)
[1] 3
> decimalplaces(2.000)
[1] 0

更新(2018年4月3日)以解决@owen88报告的关于浮点数精度舍入错误的问题--替换了x%%1检查:

decimalplaces <- function(x) {
    if (abs(x - round(x)) > .Machine$double.eps^0.5) {
        nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
    } else {
        return(0)
    }
}

我喜欢这个的外观。非常感谢你的帮助! - Pascal
谢谢@Pascal!我刚刚意识到函数中有个错别字(在as.character函数中写成了“num”而非“x”),我已经更正了。此外,我添加了regexpr部分,因此数字/字符串末尾的零将自动被删除。 - daroczig
这个函数很棒,但是当给定一个像63.0000这样的数字时,它会返回一个错误。有没有办法修改它,使得在这些情况下它返回0呢? - Pascal
2
@schnee 感谢您的反馈。或者您可以设置 options(scipen = 999) 来避免使用科学计数法。 - daroczig
2
as.character 不太好用 -- 最好使用 sprintf。对于类似 1e6、1e-6 这样的小数或大数,as.character 会出现错误。 - Dmitry Zotikov
显示剩余7条评论

16

以下是一种方法。它检查小数点后的前20位,但如果你想要检查更多或更少的位数,可以调整数字20。

x <- pi
match(TRUE, round(x, 1:20) == x)

这里还有另一种方法。

nchar(strsplit(as.character(x), "\\.")[[1]][2])

1
第一个选项返回整数的值为1。第二个选项对于整数返回NA,并且对于介于-1e-4和1e-4之间但不为零的数字也会给出错误的结果,因为它们由as.character以科学计数法显示(但您可以使用format(scientific=F)代替)。 - nisetama

9
跟进Roman的建议:
num.decimals <- function(x) {
    stopifnot(class(x)=="numeric")
    x <- sub("0+$","",x)
    x <- sub("^.+[.]","",x)
    nchar(x)
}
x <- "5.2300000"
num.decimals(x)

如果你的数据不能保证是正确的格式,你应该进行更多的检查以确保其他字符不会偷偷溜进来。


这个函数对于正整数返回1,对于负整数返回2。而当sub将其转换为字符串时,它会将1e-4转换为科学计数法,因此返回5。 - nisetama

3

不确定为什么之前没有使用这种简单方法(从tidyverse/magrittr中加载管道)。

count_decimals = function(x) {
  #length zero input
  if (length(x) == 0) return(numeric())

  #count decimals
  x_nchr = x %>% abs() %>% as.character() %>% nchar() %>% as.numeric()
  x_int = floor(x) %>% abs() %>% nchar()
  x_nchr = x_nchr - 1 - x_int
  x_nchr[x_nchr < 0] = 0

  x_nchr
}

> #tests
> c(1, 1.1, 1.12, 1.123, 1.1234, 1.1, 1.10, 1.100, 1.1000) %>% count_decimals()
[1] 0 1 2 3 4 1 1 1 1
> c(1.1, 12.1, 123.1, 1234.1, 1234.12, 1234.123, 1234.1234) %>% count_decimals()
[1] 1 1 1 1 2 3 4
> seq(0, 1000, by = 100) %>% count_decimals()
 [1] 0 0 0 0 0 0 0 0 0 0 0
> c(100.1234, -100.1234) %>% count_decimals()
[1] 4 4
> c() %>% count_decimals()
numeric(0)

因此,R似乎在内部不区分最初获取1.0001。因此,如果输入一个包含各种小数的向量,可以通过取小数位数的最大值来确定初始位数(至少)。

编辑:修复了错误


1
-2 假设小数点前只有一位数字。对于 >= 10< 0 的数字不起作用(因为负号也会被计算)。一个可能的解决方案是使用 (abs(x) %% 1) %>% ...。但即使这样,我仍然遇到了浮点问题。在我的机器上,abs(-23342.2) %% 1 打印为 0.2,但 as.character(abs(-23342.2) %% 1) 给出 "0.200000000000728" - Gregor Thomas
1
尽管现在我查了一下,似乎最佳答案有一个关于 %% 1 的问题,并找到了解决方法。比我第一条评论更简单的解决方法是使用 x_nchr - nchar(round(x)) 而不是 x_nchr - 2。这应该可以很好地处理负数和多个前导数字。 - Gregor Thomas
好的观点Gregor。我已经为此添加了一个简单的修复。同时还添加了一个解决负值的简单修复。 - CoderGuy123
这在数据中使用NA值是有效的。 - user424821

3

如果有人需要 Gergely Daróczi 提供的函数的矢量化版本:

decimalplaces <- function(x) {
  ifelse(abs(x - round(x)) > .Machine$double.eps^0.5,
         nchar(sub('^\\d+\\.', '', sub('0+$', '', as.character(x)))),
         0)
}

decimalplaces(c(234.1, 3.7500, 1.345, 3e-15))
#> 1 2 3 0

2

我测试了一些解决方案,发现这个方案能够很好地避免其他方案中报告的错误。

countDecimalPlaces <- function(x) {
  if ((x %% 1) != 0) {
    strs <- strsplit(as.character(format(x, scientific = F)), "\\.")
    n <- nchar(strs[[1]][2])
  } else {
    n <- 0
  }
  return(n) 
}

# example to prove the function with some values
xs <- c(1000.0, 100.0, 10.0, 1.0, 0, 0.1, 0.01, 0.001, 0.0001)
sapply(xs, FUN = countDecimalPlaces)

1
有趣的问题。这里是对上述回答者工作的另一个微调,矢量化,并扩展以处理小数点左侧的数字。针对负数进行了测试,前面的strsplit()方法会给出错误的结果。
如果只想计算右边的数字,则可以将trailingonly参数设置为TRUE
nd1 <- function(xx,places=15,trailingonly=F) {
  xx<-abs(xx); 
  if(length(xx)>1) {
    fn<-sys.function();
    return(sapply(xx,fn,places=places,trailingonly=trailingonly))};
  if(xx %in% 0:9) return(!trailingonly+0); 
  mtch0<-round(xx,nds <- 0:places); 
  out <- nds[match(TRUE,mtch0==xx)]; 
  if(trailingonly) return(out); 
  mtch1 <- floor(xx*10^-nds); 
  out + nds[match(TRUE,mtch1==0)]
}

这是strsplit()版本。
nd2 <- function(xx,trailingonly=F,...) if(length(xx)>1) {
  fn<-sys.function();
  return(sapply(xx,fn,trailingonly=trailingonly))
  } else {
    sum(c(nchar(strsplit(as.character(abs(xx)),'\\.')[[1]][ifelse(trailingonly, 2, T)]),0),na.rm=T);
  }

字符串版本截断在15位数(实际上,不确定为什么另一个参数off by one...超过的原因是它在两个方向上计算数字,所以如果数字足够大,它可以增加到两倍大小)。可能有一些格式选项as.character()可以给nd2()提供与nd1()places参数等效的选项。
nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
# 2  2  1  3  1  4 16 17  1
nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0));
# 2  2  1  3  1  4 15 15  1

nd1()更快。

rowSums(replicate(10,system.time(replicate(100,nd1(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));
rowSums(replicate(10,system.time(replicate(100,nd2(c(1.1,-8.5,-5,145,5,10.15,pi,44532456.345243627,0))))));

1

不是要抢线程,只是将其发布在这里,因为它可能有助于处理我尝试使用建议的代码完成的任务。

不幸的是,即使更新后 @daroczig 的解决方案也无法帮助我检查数字是否少于8个小数位。

@daroczig的代码:

decimalplaces <- function(x) {
    if (abs(x - round(x)) > .Machine$double.eps^0.5) {
        nchar(strsplit(sub('0+$', '', as.character(x)), ".", fixed = TRUE)[[1]][[2]])
    } else {
        return(0)
    }
}

在我的情况下,产生了以下结果。
NUMBER / NUMBER OF DECIMAL DIGITS AS PRODUCED BY THE CODE ABOVE
[1] "0.0000437 7"
[1] "0.000195 6"
[1] "0.00025 20"
[1] "0.000193 6"
[1] "0.000115 6"
[1] "0.00012501 8"
[1] "0.00012701 20"

到目前为止,我用以下笨拙的代码完成了所需的测试:

if (abs(x*10^8 - floor(as.numeric(as.character(x*10^8)))) > .Machine$double.eps*10^8) 
   {
   print("The number has more than 8 decimal digits")
   }

PS:关于不对.Machine$double.eps取根号可能会遗漏某些内容,请注意


1

另一个贡献是保持完全数值表示,而不转换为字符:

countdecimals <- function(x) 
{
  n <- 0
  while (!isTRUE(all.equal(floor(x),x)) & n <= 1e6) { x <- x*10; n <- n+1 }
  return (n)
}

这遇到了我认为是浮点数精度问题。例如,countdecimals(4.56) 返回8。 - Oliver

1

针对通用应用程序,这是daroczig的代码修改版本,可处理向量:

decimalplaces <- function(x) {
    y = x[!is.na(x)]
    if (length(y) == 0) {
      return(0)
    }
    if (any((y %% 1) != 0)) {
      info = strsplit(sub('0+$', '', as.character(y)), ".", fixed=TRUE)
      info = info[sapply(info, FUN=length) == 2]
      dec = nchar(unlist(info))[seq(2, length(info), 2)]
      return(max(dec, na.rm=T))
    } else {
      return(0)
    }
}

一般来说,浮点数在二进制存储时可能存在问题。请尝试以下操作:

> sprintf("%1.128f", 0.00000000001)
[1] "0.00000000000999999999999999939458150688409432405023835599422454833984375000000000000000000000000000000000000000000000000000000000"

我们现在有多少位小数?

好主意!我认为还必须有一个错误:decimalplaces2(c(1.2, 2.34, 3)) 返回1 - 另外:传递少于3个数字会导致错误。 - R Yoda
我得到了“在seq.default(2,length(info),2)中的错误:'by'参数中的错误符号”。 - CoderGuy123

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