什么是差一错误,如何解决?

What is an off-by-one error and how do I fix it?

提问人:Dina 提问时间:5/31/2010 最后编辑:S.S. AnneDina 更新时间:10/13/2022 访问量:46160

问:

什么是差一误差?如果我有一个,我该如何解决?

while循环 与语言无关

评论

0赞 Jim Garrison 9/3/2023
软件开发中最难的两件事是缓存一致性、命名和差一错误。

答:

83赞 Mark Byers 5/31/2010 #1

例如,当您打算执行 n 次循环并编写如下内容时,就会出现 off-by-one 错误

for (int i = 1; i < n; ++i) { ... }

或:

for (int i = 0; i <= n; ++i) { ... }

在第一种情况下,循环将被执行 1 次,在第二种情况下,循环将执行 1 次。其他变化是可能的,但一般来说,由于循环变量的初始值或循环的结束条件中的错误,循环执行一次或一次太少。(n - 1)(n + 1)

循环可以正确地写成:

for (int i = 0; i < n; ++i) { ... }

for 循环只是 while 循环的特例。在 while 循环中可能会犯同样类型的错误。

2赞 Niels Holst 8/17/2019 #2

由于某些语言从零(例如 C)枚举向量,而其他语言从 1(例如 R)枚举向量,因此会出现常见的 off-by-1 混淆。因此,大小的向量在 C 中具有从 to 运行的成员,但在 R 中具有 from to 的成员。xnx[0]x[n-1]x[1]x[n]

在编写循环增量的常用习语时,您还面临着一个偏离一个的挑战:

在 C 中:

i = (i+1)%n

在 R 中:

i <- (i-1)%%n + 1
5赞 Vapidant 8/18/2019 #3

假设您有以下代码,其中包含一个数组和一个循环:for

char exampleArray[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };

for(int i = 0; i <= 11; i++)
{
  print(exampleArray[i])
}

看到这里的问题了吗?因为我计算了我的数组中有 11 个字符,所以我将循环设置为迭代 11 次。但是,在大多数语言中,数组从零开始,这意味着当我的代码打印出来时

exampleArray[11]

我将收到索引越界错误,因为示例中的数组在索引 11 处没有值。

在这种情况下,我可以通过简单地告诉我的循环少迭代一次来轻松解决这个问题。

调试此问题的最简单方法是打印出上限和下限,并查看哪个值生成索引越界错误,然后将值设置为比整个迭代中的值大 1 或小 1。

当然,这假设错误是由循环超过或小于数组边界 1 或小 1 产生的,还有其他情况可能会发生索引越界错误,但是,这是最常见的情况。越界索引将始终是指尝试访问数据不存在的数据,因为过去的边界不在数据边界内。

评论

0赞 S.S. Anne 8/18/2019
很好的答案!这指的是什么语言?对我来说,它看起来像 C,但我不知道。
0赞 Vapidant 8/18/2019
@JL2210 这通常适用于大多数语言(我敢说所有语言,但我不会,因为我不能 100% 确定没有语言这个错误可能意味着其他东西)在我的例子中,我正在做 Java,但后来做了一个通用打印而不是 System.out.print(),因为我变得懒惰并决定保持它通用。就像我说的,这个概念应该贯穿于你使用的大多数语言中。
0赞 S.S. Anne 8/18/2019
索引越界错误不会发生在 C 语言中,它们只会导致未定义的行为。
1赞 Vapidant 8/18/2019
@JL2210我不经常在 C 语言中工作,所以我没有意识到这一点,我只是查了一下并做了一些测试,看来你是对的。话虽如此,我认为这仍然是一个出界错误,即使它在技术上没有抛出界外错误。在 C 语言中,当尝试访问超出范围的内容时,它要么返回一些仍然由软件拥有的随机内存,导致一些随机的意外返回,要么软件可能会尝试检索它不拥有的内存,这将导致崩溃。无论哪种方式,这个概念仍然适用于IMO。
0赞 S.S. Anne 8/18/2019
否则,它可能会在不知不觉中格式化您的硬盘驱动器。未定义的行为意味着“没有限制”;您的程序可能会导致您的计算机烧毁,并且该行为仍然有效。
9赞 Pedro Pinheiro 12/3/2019 #4

偏离 1 错误是指您期望某物的值为 N,但实际上它最终为 N-1 或 N+1。例如,您期望程序执行 10 次操作,但最终执行了 9 或 11 次(一次太少或一次太多)。在编程中,这种情况在处理“for”循环时最常见。

此错误是由于误判而发生的,您没有意识到您用来跟踪计数的数字可能与您正在计数的事物数量不同。换句话说,您用来计数的数字可能与您正在计数的事物总数不同。没有什么可以强制要求这两件事是相同的。试着从 0 大声数到 10,你最终总共说了 11 个数字,但你说的最后一个数字是 10。

防止这个问题的一种方法是意识到我们的大脑有一种倾向(也许是认知偏差)会犯这个错误。牢记这一点可能有助于您识别和预防未来的情况。但我想,为了防止这个错误,你能做的最好的事情就是编写单元测试。这些测试将帮助你确保你的代码按预期运行。

2赞 shuberman 7/9/2020 #5

当您尝试以字符串或数组的特定索引为目标(以切片或访问段)或循环访问它们的索引时,会出现一个错误(有时称为 OBOE)。

如果我们将 Javascript 视为一种示例语言,则索引从开始,而不是 1 开始,这意味着最后一个索引始终比项目的长度小 1。如果您尝试访问与长度相等的索引,程序可能会抛出一个

“索引超出范围”引用错误

打印。undefined

当您使用将索引范围作为参数的字符串或数组方法时,阅读该语言的文档并了解它们是否具有包容性(给定索引处的项是返回内容的一部分)会有所帮助。以下是一些偏离错误的示例:

let alphabet = "abcdefghijklmnopqrstuvwxyz";
let len = alphabet.length;
for (let i = 0; i <= len; i++) {
  // loops one too many times at the end
  console.log(alphabet[i]);
}
for (let j = 1; j < len; j++) {
  // loops one too few times and misses the first character at index 0
  console.log(alphabet[j]);
}
for (let k = 0; k < len; k++) {
  // Goldilocks approves - this is just right
  console.log(alphabet[k]);
}
2赞 luma 3/19/2022 #6

一个简单的经验法则:

int i = 0; // if i is initiated as 0, always use a < or > in the condition
while (i < 10) 
    System.out.printf("%d ", i++);
int i = 1; // if i is initiated as 1, always use a <= or >= in the condition
while (i <= 10)
    System.out.printf("%d ". i++);