在长操作中测试溢出的最佳通用方法是什么?

What it is the best general way for testing overflow in Long operations?

Kotlin中,和Java一样,没有溢出错误 在算术运算中。我知道有特殊的Java操作测试溢出并抛出需要处理的异常

我想要一个更简单的方法。所以我想到了一个模型,效率不是很高,但是非常简单有效。

假设有人想测试 2 个长数的乘法:a * b

我用

if ( a.doDouble()* b.toDouble()  - a*b != 0.0 )
   println("Overflow")
else
   println("Ok")

理由很简单。在 Long 的范围内,数字与其 Double 之间的差异始终是 0,即使在极端值下,当 Double 未达到所有精度时。在这种情况下,添加或减去一个小数甚至不会改变相等性测试:.

   var l1= -Long.MAX_VALUE
   var d1 = l1.toDouble()
   if (d1-l1==0.0) println("-MaxLong")
   if (d1+100-l1==0.0) println("it still -MaxLong")
   var l2= Long.MAX_VALUE
   var d2 =l2.toDouble()
   if (d2-l2==0.0) println("MaxLong")
   if (d2+100-l2==0.0) println("it still MaxLong")

这会生成输出:

-MaxLong
it still -MaxLong
MaxLong
it still MaxLong

它是正确的还是我遗漏了什么? 即使它是正确的,还有比这更好的解决方案吗?

更新 1:请注意,其他可能性正在测试 Double 计算是否大于 longValue.MAXVALUE。然而,它失败了!

   var n1= Long.MAX_VALUE/2+1 
   var n2= Long.MAX_VALUE/2+1
   println((n1.toDouble()+n2.toDouble()) - 
         Long.MAX_VALUE.toDouble()==0.0)
   println((n1.toDouble()+n2.toDouble()) > Long.MAX_VALUE.toDouble())

它打印:

true
false

更新 2:虽然我的解决方案似乎有效,但实际上没有! Alexey Romanov,在他接受的回答中指出我以下情况:

  val lo1 = Long.MAX_VALUE - 600
  val lo2 = 100L
  var do1: Double = lo1.toDouble()
  var do2:Double = lo2.toDouble()
  var d= do1+do2
  var l=lo1+lo2  
  println(d-l==0.0)  

由于结果在Long范围内,所以应该是true,但结果是false,因为Double计算不准确!

正如他所说,最好的方法是真正使用封装在用户函数中的特殊函数,如 multiplyExact

不幸的是,它的资源只能在API 24之后的Android中使用,所以它依赖于Alexey Romanov的另一个解决方案,即测试逆运算。

因此,例如,在乘法中应该这样做:

   var a = Long.MIN_VALUE 
   var b = -1L
   var c = a*b
   if (b!=0 && c/b != a) 
    println("overflow $c")
   else
    println("ok $c")

它打印 overflow -9223372036854775808

在传统运算中,通常会关注加法减法乘法 ,它们是函数 addExactsubtractExactmultipyExact 函数的对象,可以使用逆运算轻松模拟,如引用的那样。

Negation(inv())还有negateExact函数来处理Long.MIN_VALUE的取反,因为它有没有积极的对应。较少评论的是 部分 ,它在 Java 中没有专门的功能来引导溢出。然而,它在一个案例中给出了问题:Long.MIN_VALUE / -1 无效。

你应该知道长数据类型有固定的字节数Oracle Docs

The long data type is a 64-bit signed two's complement integer. It has a minimum value of -9,223,372,036,854,775,808 and a maximum value of 9,223,372,036,854,775,807 (inclusive). Use this data type when you need a range of values wider than those provided by int.

//if it is not within the range then its an overflow (infinity/undefined)
if(a*b < Long.MIN_VALUE || a*b > Long.MAX_VALUE)
    println("Overflow")
else
    println("Ok")

编辑

不幸的是,上述方法并不可靠。请参阅下面 table 来自 运行 在 android studio 上使用 JDK 8

进行的测试
##### Overflow Test #########

Long.MAX_VALUE     =  9223372036854775807
Long.MIN_VALUE     = -9223372036854775808

Long.MAX_VALUE - 2 = 9223372036854775805              
Long.MAX_VALUE - 1 = 9223372036854775806              
Long.MAX_VALUE - 0 = 9223372036854775807              
Long.MAX_VALUE + 0 = 9223372036854775807              
Long.MAX_VALUE + 1 = -9223372036854775808             
Long.MAX_VALUE + 2 = -9223372036854775807             
Long.MAX_VALUE * 2 = -2                               
Long.MAX_VALUE / 2 = 4611686018427387903              
Long.MIN_VALUE - 2 = 9223372036854775806              
Long.MIN_VALUE - 1 = 9223372036854775807              
Long.MIN_VALUE - 0 = -9223372036854775808             
Long.MIN_VALUE + 0 = -9223372036854775808             
Long.MIN_VALUE + 1 = -9223372036854775807             
Long.MIN_VALUE + 2 = -9223372036854775806             
Long.MIN_VALUE * 2 = 0                                
Long.MIN_VALUE / 2 = -4611686018427387904             
Long.MIN_VALUE + Long.MAX_VALUE = -1                  
Long.MAX_VALUE - Long.MIN_VALUE = -1                  
Long.MAX_VALUE * Long.MIN_VALUE = -9223372036854775808
Long.MAX_VALUE / Long.MIN_VALUE = 0                   
Long.MIN_VALUE / Long.MAX_VALUE = -1                  
Long.MAX_VALUE + Long.MAX_VALUE = -2                  
Long.MIN_VALUE + Long.MIN_VALUE = 0                   
Double.MAX_VALUE = 1.7976931348623157E308             
Double.MAX_VALUE * 2 = Infinity              
Double.MAX_VALUE + Double.MAX_VALUE = Infinity        
Long.MAX_VALUE * Double.MAX_VALUE = Infinity
Double.MAX_VALUE > Long.MAX_VALUE = true
Double.MIN_VALUE < Long.MIN_VALUE = true         

查看日志,您会注意到任何时候 Long.MAX_VALUE 达到峰值而不是像 Double.MAX_VALUE 那样达到无穷大,该位被切换并且其下一个值变为 Long.MIN_VALUE 并继续就这样。

所以现在我们明白为什么上面的方法不可靠了。因此我们可以假设在 java Long is a DataType with zero Infinity.

修改方法,中间引入浮点常量

//using floating points forces larger memory allocation
//this prevents bit switch after crossing max or min value of Long
if(a * 1.0 * b < Long.MIN_VALUE || a * 1.0 * b > Long.MAX_VALUE)
   println("Either a Double or Long Overflow")
else
   println("Ok")

Within the universe of Long the difference between a number and its Double is always 0

不,不是真的。

println(Long.MAX_VALUE)
println(BigDecimal(Long.MAX_VALUE.toDouble()))

打印

9223372036854775807
9223372036854775808

你试过检查这个:

var l2= Long.MAX_VALUE
var d2 =l2.toDouble()
if (d2-l2==0.0) println("MaxLong")

但问题是 JVM 上的算术运算(实际上在大多数语言中)只能对相同类型的值进行运算,因此编译器会插入 toDouble() 而您实际上会计算 d2 - l2.toDouble() .

如果你想做一个简单的测试,你可以做

val product = a*b
if ((b != 0 && product/b != a) || (a == Long.MIN_VALUE && b == -1)) {
    println("Overflow")
} else {
    // can use product here
    println("OK")
}

但实际上,使用 multiplyExact 而不是手动操作更有意义。或者使用 Kotlin 的可空类型并定义

fun multiplyExact(x: Long, y: Long): Long? = 
    try { java.math.multiplyExact(x, y) } catch (e: ArithmeticException) { null }

编辑:为了证明测试中的错误,请考虑加法(我很确定乘法也是错误的,但很难找到合适的数字):

val largeNumber = Long.MAX_VALUE - 600
val smallNumber = 100L
// prints true, even though there's no overflow
println((largeNumber.toDouble() + smallNumber.toDouble()) - (largeNumber + smallNumber) != 0.0)

原因是 largeNumber.toDouble() + smallNumber.toDouble() == largeNumber.toDouble()(largeNumber + smallNumber).toDouble() == Long.MAX_VALUE.toDouble().