为什么Haskell可以轻松处理非常大的数字?
Hugs> 94535^445
为什么Haskell可以计算如此大的数字,而其他语言(如Java)不能(如此容易)?
Java有BigInteger类。
它可以将这个工具构建到语言中,但是(像许多语言一样)它往往使原始特征紧密地映射到CPU支持的东西上。
另一方面,Haskell强调数学符号风格的表达性,其中“性能”考虑因素在很大程度上是无关紧要的。
这是设计理念的不同之处 :
-
Haskell的设计者希望确保用户不会对需要超过32位的整数计算看似随意的失败感到惊讶。
-
Java的设计者希望确保用户不会因为对需要超过32位的整数进行大量计算而导致看似随意的性能下降而感到惊讶。
在每种语言中,您必须做一些特殊的事情来获得另一种整数。
默认情况下,支持任意大整数的语言有悠久而光荣的历史。 我最喜欢的两个是Icon和Smalltalk ,它们都超过25年。
Haskell中的数字文字被重载,因此它们可以表示多种具体类型(如Int
, Integer
, Float
甚至MyOwnNumber
)。
您可以通过提供类型信息手动选择特定类型,如下所示:
x = 4 :: Int y = 4 :: Integer z = 4 :: Float
这三个值具有不同的类型,并且对这些值执行的操作将表现不同。
Int
的确切大小取决于实现,但可以是28位,这种类型的行为类似于Java原语int
,例如它会溢出。
Integer
是一种可以包含任意精度整数的类型,如Java BigInteger 。
Float
就像一个Java float
,使用浮点运算。
就像数字文字一样,许多运算符也会被重载(使用类型类 ),因此可以与不同的类型一起使用。 因此+
运算符可以与Int
s和Float
。
在您的情况下,由于您没有提供任何类型信息,解释器将默认为Integer
类型。 这意味着对于^
运算符,它也将选择Integer
实例。 允许任意精度的整数计算。
Java具有“原始数据类型”(处理器支持的类型)的概念,并且与所有其他类不同。
在Haskell中, Int
是一个类似于所有其他类型的类型,因此它很容易成为(^)
使用的Num
和Integral
类型类的成员( "(^) :: (Num a, Integral b) => a -> b -> a"
)。 这些类型类的另一个成员是Integer
,它支持所有大小的整数(只要你的数字有足够的内存)。
在Java中,您可以使用许多“大数字”库,但是它们的操作不会使用您习惯的中缀运算符,因为它们仅用于Java中的“原始类型”。
简短而基本的答案是它们实现了默认的整数不同。 在Java中,标准int是32位。 签名,给你一个−2,147,483,648
到+2,147,483,647
的范围。
也就是说,Java也有bignum类。 如果你使用它们,你也将获得使用任意大数字的能力。
如前所述,如果您有32位字并使用全范围,则使用二进制补码得到-2 ^ 31到2 ^ 31-1。
通过保留该字的几位,这些位可用于携带该值的类型信息。 也就是说,值在运行时“知道”它们自己的类型。 其余位用于携带值的数据。
适合这些剩余位的整数值可以直接存储在单词中。 这种整数通常称为“fixnums”。 如果它们不适合,则该字的类型位表示它是’bigint’,其余位用于将存储器指针存储到存储bigint值的堆中。
编译器需要将算术表达式转换为多个代码路径,涵盖操作数的允许类型组合。 添加示例:
- fixnum + fixnum
- bigint + fixnum
- fixnum + bigint
- bigint + bigint
这些语言的编译器中的许多优化都集中在避免使其工作所需的运行时类型检查的开销上。 通常还有一些方法可以明确地告诉编译器,从fixnum到bignum的自动降级是不希望的,而是需要32位整数的溢出行为。 这对于有效地实施加密算法非常重要。
这是如何编码数字的问题。 传统的方法是使用给定的位数对数字进行编码,这样您就无法获得无限的精度。 Haskell显然用一个数字的可变位数来做这个也很好,但通常意味着所有数学都是用软件完成的,因为硬件加速通常只能用于有限精度。
您可以使用BigInteger执行相同的操作。 Haskell是一种比Java更简洁的函数式语言。
我们拥有这么多语言的一个原因是,不同的语言在不同的任务中表现得更好,因为它们的设计具有不同的假设。 大多数函数式语言使用数学函数更简单,但往往会遇到其他用例,例如,haskell不太可能是编写GUI的好选择。