我想知道这段代码是否为SOLID:
public static boolean CanAdd(int me, int... args) {
int total = me;
for (int arg : args) {
if (total >= 0) {
if (java.lang.Integer.MAX_VALUE - total >= arg) { // since total is positive, (MaxValue - total) will never overflow
total += arg;
} else {
return false;
}
} else {
if (java.lang.Integer.MIN_VALUE- total <= arg) { // same logic as above
total += arg;
} else {
return false;
}
}
}
return true;
}
是否有人有更好(更快)的方法来实现相同的目的?
#1 楼
我没有找到您的代码无法很好处理的任何输入。以下是一些测试:assertTrue(CanAdd(0, Integer.MAX_VALUE));
assertTrue(CanAdd(0, Integer.MIN_VALUE));
assertTrue(CanAdd(Integer.MIN_VALUE, 0));
assertTrue(CanAdd(-1, Integer.MAX_VALUE));
assertFalse(CanAdd(1, Integer.MAX_VALUE));
assertFalse(CanAdd(-1, Integer.MIN_VALUE));
因此,它可以工作,但要阅读它并非易事。如果这不是应用程序中的瓶颈,我可以使用
long
:public static boolean canAdd(int... values) {
long sum = 0;
for (final int value: values) {
sum += value;
if (sum > Integer.MAX_VALUE) {
return false;
}
if (sum < Integer.MIN_VALUE) {
return false;
}
}
return true;
}
我认为它更易于阅读和维护。最后,请注意:根据Java编程语言的代码约定,方法的名称应为
canAdd
(带小写的首字母)。方法应为动词,与首字母
小写,每个内部单词的首字母大写。
编辑: br />
public static int addAndCheck(int x, int y)
throws MathArithmeticException {
long s = (long)x + (long)y;
if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_ADDITION, x, y);
}
return (int)s;
}
以及番石榴:
public static int checkedAdd(int a, int b) {
long result = (long) a + b;
checkNoOverflow(result == (int) result);
return (int) result;
}
评论
\ $ \ begingroup \ $
这个答案是有效的,但是,Todd Lehman的其他答案似乎更少的代码行。但这仍然是一个很好的答案:)
\ $ \ endgroup \ $
–cellepo
18年11月11日在20:30
\ $ \ begingroup \ $
感谢您提供有关Apache&Guava如何使用长转换的说明性示例。我认为这有助于避免他们完全接受2个int参数,但我认为这排除了长时间溢出的可能性-尝试对两个以上参数进行求和会沿着pf可能达到长时间溢出的路径开始……但是我相信这隐式地说明了长时间溢出,因为它们具有整数大小检查的“尽早”循环/方法出口(在每个循环迭代中)。
\ $ \ endgroup \ $
–cellepo
18年11月13日在21:56
\ $ \ begingroup \ $
@cellepo:谢谢!是的,显式的values.length检查将有助于在此处进行推理。只是为了好玩,您需要一个至少具有(Long.MAX_VALUE / Integer.MAX_VALUE)=(2 ^ 63-1)/(2 ^ 31-1)= 4294967298 int元素的数组来溢出长和变量。此4294967298是Java中可能的最大数组长度的两倍:-)
\ $ \ endgroup \ $
–palacsint
18年11月14日在1:18
\ $ \ begingroup \ $
@ palacsint嗯,这是一个很好的数学分析方法(谢谢!)-我认为这实际上是不可能的,因为即使最大大小的数组都完全填充了MAX_VALUE实例,也无法足以使总和长期溢出。
\ $ \ endgroup \ $
–cellepo
18-11-19在3:02
\ $ \ begingroup \ $
所以也许我太理论化了:)至少对于JVM上的数组-我认为,如果一个Collection可以容纳更多(如果有这样的Collection),那么一个自定义对象(例如手动/自定义链表数据结构),或者在数学分析中涉及数字类型的最大尺寸。但是,再说一遍,要有一点理论性(尤其是在我刚才提到的上一次数学分析中)... :)
\ $ \ endgroup \ $
–cellepo
18年11月19日在3:09
#2 楼
此处所有其他答案(截至撰写本文时)均有效。但是,如果您是Java 8+,则可能希望使用Java 8+的
Math.addExact(int, int)
更加精确: public static boolean canSumToInt(int me, int... args){
for(int curArg: args){
try{
me = Math.addExact(me, curArg);
}catch(ArithmeticException ae){
return false;
}
}
return true;
}
溢出将引发
ArithmeticException
。评论
\ $ \ begingroup \ $
将try / catch放在循环外可能更容易理解(速度上完全相等)。即尝试{for(int curArg:args)me = Math.addExact(me,curArg);返回true; } catch(ArithmeticException ae){返回false; }
\ $ \ endgroup \ $
– Toby Speight
18年11月12日在18:33
\ $ \ begingroup \ $
是的,这是一个有效的选择。我个人更喜欢我,因为我希望try块尽可能接近/限制可能是Exception来源的实际行/调用-而不是尝试也包含循环/保护本身,循环/后卫永远不会抛出异常的地方。对我来说,限制try块对我来说更具可读性,因为该限制使Exception源可能位于的位置更加清楚/明确(也有助于调试)。 Toby Speight替代品也是有效的,我没有批评它(只是在替代品的背景下阐明了我的个人喜好)。
\ $ \ endgroup \ $
–cellepo
18-11-13在20:26
#3 楼
关于当前代码:我将
CanAdd
重命名为canAdd
(根据编码约定)。将
me
重命名为value
(更具描述性),并将args
重命名为values
删除不必要的
arg
软件包前缀。public static boolean canAdd(int value, int... values) {
int total = value;
for (int currentValue: values) {
if (total >= 0) {
// since total is positive, (MaxValue - total) will never
// overflow
if (Integer.MAX_VALUE - total >= currentValue) {
total += currentValue;
} else {
return false;
}
} else {
// same logic as above
if (Integer.MIN_VALUE - total <= currentValue) {
total += currentValue;
} else {
return false;
}
}
}
return true;
}
我也将注释向上移了一行,以避免水平滚动。
我不太喜欢这里的
currentValue
和java.lang
,所以我对前两行做了一些更改:public static boolean canAdd(int... values) {
int total = 0;
...
}
如果反转内部
value
语句可以消除values
关键字:if (total >= 0) {
if (Integer.MAX_VALUE - total < currentValue) {
return false;
}
total += currentValue;
} else {
if (Integer.MIN_VALUE - total > currentValue) {
return false;
}
total += currentValue;
}
两个分支中的
if
是相同的,因此可以在else
之后移动:if (total >= 0) {
if (Integer.MAX_VALUE - total < currentValue) {
return false;
}
} else {
if (Integer.MIN_VALUE - total > currentValue) {
return false;
}
}
total += currentValue;
引入解释性
+=
变量可以使其更短并节省缩进级别: public static boolean canAdd(int... values) {
int total = 0;
for (int currentValue: values) {
final boolean positiveTotal = total >= 0;
if (positiveTotal && (Integer.MAX_VALUE - total < currentValue)) {
return false;
}
if (!positiveTotal && (Integer.MIN_VALUE - total > currentValue)) {
return false;
}
total += currentValue;
}
return true;
}
但是我认为仍然很难理解。我会进行
if
转换。#4 楼
您的逻辑对我来说很扎实。但是,它很微妙。这是另一个使用
long
的版本,但逻辑更简单:想写吧。请注意,此循环中没有“提前退出”,这意味着它将始终运行到列表的末尾。但是,没有条件的话,在很多情况下,如果有必要的话,它可能会更快。检测溢出,以避免误报(如果值列表为数十亿,则可能在早期版本中使用):public static boolean canAdd(int... values) {
long longSum = 0;
int intSum = 0;
for (final int value: values) {
intSum += value;
longSum += value;
}
return intSum == longSum;
}
评论
\ $ \ begingroup \ $
longSum可能还会溢出(至少最终,因此intSum之后也必须事先溢出)。但是,我不确定是否有可能(在两个都溢出的情况下)结束intSum == longSum最终返回假阳性(真)的情况?换句话说,intSum溢出和longSum溢出值(在同一canAdd调用内)是否最终会相等(因此无效地返回true)?
\ $ \ endgroup \ $
–cellepo
18年11月13日在21:22
\ $ \ begingroup \ $
如果我的上述评论对错误肯定的可能性是正确的,那么我认为这就是[答案]结果,因为该答案中没有“提前退出”。 (这里的其他一些答案的确有一个“提早”出现)。
\ $ \ endgroup \ $
–cellepo
18年11月13日在22:09
\ $ \ begingroup \ $
首先,我对“答案”的评论并非旨在作为个人批评,而只是作为建设性的批评(我可能对自己的怀疑是错误的)。否则,这是几天前我最喜欢的答案(当我投票赞成!),并把我引到这里的其他答案(包含“思考的食物”)-但是,如果我的怀疑正确,那么我的另一个答案同样也遭受相同的长期溢出而没有“尽早流出”的现象(并在此处指出)。无论我的怀疑论是否正确,我仍然感谢此答案的其他启发性品质:)
\ $ \ endgroup \ $
–cellepo
18年11月13日在22:18
\ $ \ begingroup \ $
@cellepo-谢谢!您的怀疑是正确的!如果列表的长度是十亿(我相信是2 ^ 32或更多),那么如果31位正整数值的总和大于63位正整数,则某些输入组合可能会提供错误肯定价值长。我添加了第二个版本,通过检测到任何int溢出立即退出,从而避免了此缺点。
\ $ \ endgroup \ $
–托德·雷曼(Todd Lehman)
18年11月14日在18:33
\ $ \ begingroup \ $
是的,第二版的简单检查会导致长时间的溢出!但是,也许我应该为过于理论化而道歉:我认为我最近意识到,通过@palacsint对他们的Answer中的注释的见识,数组的最大容量实际上将阻止长时溢出(至少在JVM上)-请参见评论有关该其他答案的讨论以获取更多详细信息。 (很抱歉,如果最终这是一个琐碎的兔子洞)。
\ $ \ endgroup \ $
–cellepo
18年11月19日在3:24
#5 楼
[注意:这更像是“思考的食物”的答案,因为我最终意识到,当调用足够的args
导致long
-overflow时,它实际上可能是无效的,但是我认为这可能仍然值得发布使用long
...显示其他可能性...] 这不是渐近地更快(像Question中那样仍然是线性O(| args |)),但是主体代码的行数少得多(3)由于只有1个逻辑/ if-check,所以速度快很多: [较小的数据类型]
int
-> [较大的数据类型] int
(实际数字的最终结果相同) long
中的第一个值但是我提到的技术上可能的无效性是,循环求和可能会达到
me
-overflow(会被“检测不到”),并且具有足够大的单个me
或足够多的args
-且溢出的总和可能最终变为long
-type-magnitude,这将返回[args
的错误肯定] br />但是,通过重新引入其他args
参数,您甚至可以节省1行代码:public static boolean canSumToInt(long... args){
long sum = 0;
for(long curLong: args) sum += curLong;
return Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE;
}
评论
\ $ \ begingroup \ $
如果使用Java 8,则应该在此页面上看到我的其他答案。
\ $ \ endgroup \ $
–cellepo
18年11月12日在2:38
\ $ \ begingroup \ $
如果不是Java8 +,并且您希望/需要考虑长时间溢出,则对这种Answers进行这样的改进基本上可以得出此处的其他Answers,它们使用Integer-size检查的“尽早”循环/方法退出(在每个循环迭代中)-因此请使用以下答案之一:)
\ $ \ endgroup \ $
–cellepo
18-11-13在22:03
\ $ \ begingroup \ $
fwiw-如果args是int(而不是long),则将排除long-overflow的可能性(至少在JVM上,由于数组的最大容量)-请参见其他评论@ @ palacsint进行更多讨论。
\ $ \ endgroup \ $
–cellepo
18年11月19日在3:33
评论
您是否进行了任何分析,表明此方法是应用程序的瓶颈?@palacsint不,这不是我应用程序中的瓶颈..只是我对与范围检查有关的算法感兴趣,并且想知道是否有更好的解决方案(除了强制转换)=)
我已经用不错的按位代码在SO上看到了一些(通常是C / C ++)问题和答案,也许您想检查一下它们:-)
我不知道您的最终目的,但是根据您要实现的目标,也许您会发现Guava的检查算法很有用。请注意,这实际上不是对查询的直接响应。 code.google.com/p/guava-libraries/wiki/MathExplained
说到检查算术-Java8 +现在在其Math类中有了它(您可以在此处查看有关Java8 +的其他答案)。