我已经向我的学生解释说,等于测试对于浮点变量并不可靠,但对于整数则可以。我使用的教科书说>和<比> =和<=更容易阅读。我在某种程度上同意,但是在For循环中?让循环指定开始和结束值是否更清晰?
我是否缺少教科书作者正确的东西?
另一个示例在范围测试中,例如:
如果分数> 89等级='A'
否则,如果分数> 79等级='B'...
为什么不直接说:如果分数> = 90?
我已经向我的学生解释说,等于测试对于浮点变量并不可靠,但对于整数则可以。我使用的教科书说>和<比> =和<=更容易阅读。我在某种程度上同意,但是在For循环中?让循环指定开始和结束值是否更清晰?
我是否缺少教科书作者正确的东西?
另一个示例在范围测试中,例如:
如果分数> 89等级='A'
否则,如果分数> 79等级='B'...
为什么不直接说:如果分数> = 90?
Answers:
在具有从零开始的数组的大括号编程语言中,习惯上这样编写for
循环:
for (int i = 0; i < array.Length, i++) { }
这遍历数组中的所有元素,是迄今为止最常见的情况。避免使用<=
或>=
。
只有当您需要跳过第一个或最后一个元素,或以相反的方向遍历或从另一个起点或另一个终点遍历时,才需要更改此参数。
对于集合,使用支持迭代器的语言,更常见的情况是:
foreach (var item in list) { }
这完全避免了比较。
如果您在寻找何时使用<=
vs 的硬性规定<
,那没有一个。使用最能表达您意图的内容。如果您的代码需要表达“每小时小于或等于55英里”的概念,则需要说<=
,而不是<
。
回答有关成绩范围的问题>= 90
更有意义,因为90是实际边界值,而不是89。
for
循环的最常见用例。for
我在这里提供的循环形式将立即被具有少量经验的任何开发人员识别。如果您想根据更具体的情况提供更具体的答案,则需要将其包括在问题中。
没关系
但是为了论证,让我们分析两个选项:a > b
vs a >= b
。
不挂断!这些不相等!
OK,那么a >= b -1
VS a > b
或a > b
VS a >= b +1
。
嗯,a >b
和a >= b
两个看起来比a >= b - 1
和a >= b +1
。这些到底是什么1
?因此,我认为必须通过添加或减去随机数s 来消除从具有>
而不是相反产生的任何好处。>=
1
但是,如果是数字怎么办?是说a > 7
还是更好a >= 6
?等一等。我们是否在认真争论使用>
vs >=
和忽略硬编码变量是否更好?所以,它真的就变成了是否一个问题a > DAYS_OF_WEEK
是比a >= DAYS_OF_WEEK_MINUS_ONE
...还是a > NUMBER_OF_LEGS_IN_INSECT_PLUS_ONE
VS a >= NUMBER_OF_LEGS_IN_INSECT
?我们回到加/减法1
,只是这次是变量名。或者,也许是在辩论是否最好使用阈值,限制,最大值。
似乎没有一般规则:这取决于所比较的内容
但是,实际上,代码中还有许多重要的事情需要改进,还有客观和合理的指导原则(例如,每行X字符限制),但仍有例外。
>
VS >=
或有关的讨论是否讨论>
VS >=
是有意义的?尽管最好避免讨论此问题:p
在计算上有使用时的成本没有区别<
或>
比较<=
或>=
。计算速度一样快。
但是,大多数for循环将从0开始计数(因为许多语言对其数组使用0索引)。因此,这些语言中的规范for循环是
for(int i = 0; i < length; i++){
array[i] = //...
//...
}
这样做<=
需要您在某处添加-1以避免偏离一个错误
for(int i = 1; i <= length; i++){
array[i-1] = //...
//...
}
要么
for(int i = 0; i <= length-1; i++){
array[i] = //...
//...
}
当然,如果语言使用基于1的索引,则可以使用<=作为限制条件。
关键是条件中表示的值是问题描述中的值。阅读起来更干净
if(x >= 10 && x < 20){
} else if(x >= 20 && x < 30){
}
半开间隔
if(x >= 10 && x <= 19){
} else if(x >= 20 && x <= 29){
}
并且必须做数学才能知道19到20之间没有可能的值
for(markup = 5; markup <= MAX_MARKUP; ++markup)
。其他任何事情都会使它复杂化。
我要说的不是要使用>还是> =。关键是要使用允许您编写表达性代码的任何内容。
如果发现需要加/减一个,请考虑使用另一个运算符。我发现当您以自己的域名的良好模型开始时,好事就会发生。然后,逻辑将自己写入。
bool IsSpeeding(int kilometersPerHour)
{
const int speedLimit = 90;
return kilometersPerHour > speedLimit;
}
这比
bool IsSpeeding(int kilometersPerHour)
{
const int speedLimit = 90;
return kilometersPerHour >= (speedLimit + 1);
}
在其他情况下,最好采用其他方式:
bool CanAfford(decimal price, decimal balance)
{
return balance >= price;
}
比好多了
bool CanAfford(decimal price, decimal balance)
{
const decimal epsilon = 0e-10m;
return balance > (price - epsilon);
}
请原谅“原始痴迷”。显然,您想分别在此处使用Velocity和Money类型,但是为了简洁起见,我省略了它们。关键是:使用更简洁的版本,使您可以专注于要解决的业务问题。
正如您在问题中指出的那样,测试浮点变量的相等性是不可靠的。
这同样适用于真正的<=
和>=
。
但是,整数类型不存在这种可靠性问题。在我看来,作者对哪种方法更具可读性表示了自己的观点。
您是否同意他当然是您的意见。
<
或<=
基于最自然的选择。正如其他人指出的那样,在FOR循环<
中使用C类型语言更有意义。还有其他用例<=
。随时随地使用所有可用的工具。
for (unsigned int i = n; i >= 0; i--)
或者for (unsigned int i = x; i <= y; i++)
,如果y
恰好是UINT_MAX
。糟糕,这些循环永远存在。
每个关系<
,<=
,>=
,>
和也 ==
和!=
有自己的使用情况来比较两个浮点值。每种都有特定的含义,应选择适当的含义。
我将为您提供一些示例,在这些示例中,您需要为每个示例精确地使用此运算符。(但是要注意NaN。)
f
,该函数将浮点值作为输入。为了加快计算速度,您决定添加一个最新计算值的缓存,即,一个映射x
到的查找表f(x)
。您将真正想要==
比较参数。x
?您可能要使用x != 0.0
。x
是否在单位间隔内?(x >= 0.0) && (x < 1.0)
是正确的条件。d
矩阵的行列式,并想知道它是否为正定的?除了没理由使用其他任何东西d > 0.0
。alpha <= 1.0
。浮点数学运算(通常)并不精确。但这并不意味着您应该担心它,将其视为魔术,并且肯定不会总是将两个浮点数相等(如果它们在内)1.0E-10
。这样做确实会破坏您的数学,并导致所有奇怪的事情发生。
x != 0.0
和y
是一个有限的浮点值,也y / x
不必是有限的。但是,知道是否y / x
由于溢出或操作在数学上没有明确定义而引起的不确定性可能是有意义的。x
必须在单位间隔[0,1)中,那么当使用x == 0.0
或调用它引发断言失败时,我真的会很不高兴x == 1.0 - 1.0E-14
。1.0E-30
,那么什么也得不到。您所做的只是增加了给出错误答案的可能性。alpha
可能会受到舍入误差的影响,因此alpha <= 1.0
即使表达式alpha
所计算的真实数学值可能确实大于1,它也可能是正确的。但是,此时您无能为力。与软件工程中一样,在适当的级别处理错误,并且仅处理一次。如果在1.0E-10
每次比较浮点数时按四舍五入的顺序添加舍入误差(这似乎是大多数人所使用的魔术值,我不知道为什么),则很快就会出现以下误差:1.0E+10
…
循环中使用的条件类型可能会限制编译器执行的优化种类,无论是好是坏。例如,给定:
uint16_t n = ...;
for (uint16_t i=1; i<=n; i++)
... [loop doesn't modify i]
编译器可以假设上述条件应导致循环在第n个传递循环之后退出,除非 n可能达到65535,并且循环可能以某种方式退出,而不是i超过n。如果满足这些条件,则编译器必须生成代码,该代码将导致循环运行,直到上述条件以外的其他原因导致循环退出。
如果循环改为:
uint16_t n = ...;
for (uint16_t ctr=0; ctr<n; ctr++)
{
uint16_t i = ctr+1;
... [loop doesn't modify ctr]
}
那么编译器可以放心地认为循环永远不需要执行n次以上,因此可以生成更有效的代码。
请注意,任何带符号类型的溢出都可能带来严重后果。鉴于:
int total=0;
int start,lim,mult; // Initialize values somehow...
for (int i=start; i<=lim; i++)
total+=i*mult;
编译器可能会将其重写为:
int total=0;
int start,lim,mult; // Initialize values somehow...
int loop_top = lim*mult;
for (int i=start; i<=loop_top; i+=mult)
total+=i;
如果在计算中不发生溢出,则这种循环的行为将与原始循环相同,但即使在整数溢出通常具有一致的包装语义的硬件平台上,也可以永久运行。