为什么没有围绕Integer Overflow设计scala?

时间:2014-10-25 18:33:47

标签: scala int biginteger integer-overflow

我是一名前Java开发人员,最近我观看了Venkat Subramaniam教授(https://www.youtube.com/watch?v=LH75sJAR0hc)对Java开发人员的深入而有趣的介绍。

引入的一个要点是取消声明的类型来代替"类型推断"。据推测,这意味着高阶编译器通过上下文识别我打算使用的类型。

作为交易的应用程序安全专家,我尝试做的第一件事就是打破这种类型的推断......例如:

// declare a function that returns the square of an input Int. The return type is to be inferred.
scala> val square = (x:Int) => x*x
square: Int => Int = <function1>
// I can see the compiler inferred an Int for the output value, which I do not agree with.

scala> square(2147483647)
res1: Int = 1
// integer overflow

我的问题是为什么编译器没有看到&#34; *&#34;是一个有溢出威胁的运算符,并将输入包装成像BigInteger一样更具保护性的东西?

根据教授的说法,我应该忘记内部实施,然后继续我的业务逻辑。但是在我的快速演示之后,我不太确定Scala对于那些不了解编译器用我的方法做什么的程序员来说是安全的。

6 个答案:

答案 0 :(得分:4)

我认为@rightføld在某种程度上夸大了溢出发生或不发生的频率(特别是考虑到主动尝试溢出你的攻击者时)。但我同意他的基本观点。将所有数学转换为BigInteger几乎肯定会对Java产生巨大的性能影响。对于开发人员来说,选择这样一种语言,他们必须得到一些可见的成本。

对于许多操作,字符串对象比cstrings具有小得多的性能开销。它们还为开发人员提供了非常明显的好处,这就是人们使用它们的原因,而不是安全性本身。字符串对象很容易在cstrings上做很多常见的事情。 BigInteger没有提供这些。它只需要一小部分速度完全相同的代码,但不会溢出(很少有开发人员日常看到的错误,即使安全人员经常看到它)。

等效的是一个cstring(strcmp,strcpy,strcat等)以一小部分速度运行,但只是不需要null终止符。我不认为很多人会跳过使用它,无论多少有助于安全性,而不是以空终止的字符串。如果语言需要它,我不会看到很多人急于使用这种语言。

正如@rightføld在评论中所暗示的那样,与Java的互操作性将被破坏,因为大多数(如果不是全部)数字都会成为BigInteger。你不断进行转换,这会增加溢出的相同危险,同时增加了大量的代码复杂性(以及更多的性能影响)。

如果语言有许多其他引人注目的功能,那么从头开始语言可能会无处不在的BigInteger(如python),但是改造成一种想要自然过渡的语言是非常困难的(和with)Java。

答案 1 :(得分:4)

除了上述答案之外,我认为这个问题误解了静态类型语言中类型推断的目的。类型推断不会做出您所指的选择 - 将Int提升为BigInt。它仅限于在编译时根据已知类型的子表达式“推断”表达式的类型。

Int中的*函数在提供Int输入参数

时返回Int
def *(x: Int): Int

在这种情况下,由于x被声明为Int,因此基于x*x的签名,Int必须是*

如果我们真的想要这种行为,我们可以定义一个在乘法时将Int提升为BigInt的函数。

implicit class SafeInt(x: Int) {
  def safeMult(a: Int): scala.math.BigInt = scala.math.BigInt(x)*a
}

然后我们可以定义一个具有所需属性的正方形:

scala> val square = (x: Int) => x safeMult x
square: Int => scala.math.BigInt = <function1>

答案 2 :(得分:3)

编译器根据可用的方法推断。 Int有一个方法*(Int): Int,就编译器而言,它是完全定义好的; 2147483647*2147483647是一个非常好的方法调用,结果为1,它不会抛出ClassCastException或类似的东西。

为什么Int类型以这种方式编写?主要用于Java / JVM兼容性;为了兼容Java,Scala的许多部分都有设计妥协。如果您不需要该功能,则可能更喜欢使用Haskell或类似语言。 (我怀疑即使不需要JVM兼容性,Scala也希望公开机器本机整数类型,以便用户可以在需要时进行性能/正确性权衡。虽然它们可能不是默认值)

如果您在Scala中进行数值计算,您可能希望使用Spire库,这样可以轻松地对数字类型进行抽象,并提供了几种具有特定属性的高性能数字类型。特别是它具有SafeLong类型,可以处理任意精度的整数,但对于BigInt范围内的值,其性能要比Long好得多,类似于Python的整数类型。

答案 3 :(得分:2)

因为溢出几乎从未在实践中发生过,BigIntegerInt相比,狗的速度很慢。在*返回Int s上进行所有BigInteger次操作也非常不方便。

答案 4 :(得分:2)

“识别我打算使用的类型”并不能准确描述scala尝试做什么。在给定上下文强加的约束的情况下,它推断出最通用的类​​型。因此,如果您撰写List(Nil, "1"),您将获得List[Serializable],因为SerializableListString共享的界面 - 无视Serializable可能根本不在你的脑海里。

您提出的问题可能更准确地被问为“为什么Int是数字文字的类型而不是BigInteger?” - 推论与它没有太大关系。

我们可以在该主题上看到我们想要的所有内容,但是有一个最准确的答案可以解释为什么Scala是这样的:“因为Java”。

答案 5 :(得分:1)

如果你想要你想要的安全类型,那么一种方法是通过一个防止数字溢出的部分函数来定义然后返回一个Option [Int]甚至可能是一个[Int,BigInteger] 。

方形函数的类型推断是正确的 - 假设它是根据您指定的输入类型和*函数的类型推断的......它并没有真正打破我的意见。