我有一段代码需要几个整数,并检查是否对输入执行加法运算会导致溢出。

我想知道这段代码是否为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;
}


是否有人有更好(更快)的方法来实现相同的目的?

评论

您是否进行了任何分析,表明此方法是应用程序的瓶颈?

@palacsint不,这不是我应用程序中的瓶颈..只是我对与范围检查有关的算法感兴趣,并且想知道是否有更好的解决方案(除了强制转换)=)

我已经用不错的按位代码在SO上看到了一些(通常是C / C ++)问题和答案,也许您想检查一下它们:-)

我不知道您的最终目的,但是根据您要实现的目标,也许您会发现Guava的检查算法很有用。请注意,这实际上不是对查询的直接响应。 code.google.com/p/guava-libraries/wiki/MathExplained

说到检查算术-Java8 +现在在其Math类中有了它(您可以在此处查看有关Java8 +的其他答案)。

#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;
}


我也将注释向上移了一行,以避免水平滚动。

我不太喜欢这里的currentValuejava.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