在长操作中测试溢出的最佳通用方法是什么?
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
在传统运算中,通常会关注加法、减法、乘法 ,它们是函数 addExact
、subtractExact
、multipyExact
函数的对象,可以使用逆运算轻松模拟,如引用的那样。
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()
.
在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
在传统运算中,通常会关注加法、减法、乘法 ,它们是函数 addExact
、subtractExact
、multipyExact
函数的对象,可以使用逆运算轻松模拟,如引用的那样。
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()
.