Hugs> 94535^445
13763208823213770506960538876615156211048901640052821530697264247739998018468419032448277029434879827074549660094560167350418780006041435009085328
As already mentioned, if you have 32 bit words and use the full range you get -2^31 to 2^31-1 using two's complement.
By reserving a few bits of the word, those bits can be used to carry type-information for the value. That is, values "know" their own type at runtime. The remaining bits are used to carry the value's data.
Integer values that fit in these remaining bits can be stored directly in the word. Such integers are typically called 'fixnums'. If they don't fit, then the type bits of the word indicate it is a 'bigint', and the remaining bits are used to store a memory pointer into the heap where the bigint value is stored.
The compiler needs to translate your arithmetic expressions into multiple code paths that cover allowed type combinations for the operands. Example for addition:
A lot of optimizations in compilers for these languages focus on avoiding overhead for the runtime type-checks needed to make this work. There are often also ways to explicitly tell the compiler that automatic demotion from fixnum to bignum is undesired, and instead one want the overflow behavior of 32bit integers. This can be very important for implementing cryptography algorithms efficiently.
The short and basic answer is that they implement default integers differents. In Java, a standard int is 32 bits. Signed, that gives you a range of −2,147,483,648
to +2,147,483,647
.
That said, Java has bignum classes too. If you use those, you'll also gain the ability to use arbitrarily large numbers.
It's a difference in design philosophy:
The designers of Haskell wanted to be sure that users would not be surprised by the seemingly arbitrary failure of an integer computation needing more than 32 bits.
The designers of Java wanted to be sure that users would not be surprised by the seemingly arbitrary performance degradation caused by doing lots of computations over integers needing more than 32 bits.
In each language, you have to do something special to get the other kind of integer.
There's a long, honorable history of languages supporting arbitrarily large integers by default. Two of my favorites are Icon and Smalltalk, which are both over 25 years old.
Numeric literals in Haskell are overloaded so that they can represent multiple concrete types (like Int
, Integer
, Float
or even MyOwnNumber
).
You can manually chose a specific type by providing type information, like so:
x = 4 :: Int
y = 4 :: Integer
z = 4 :: Float
These three values have different types and operations performed on these will behave differently.
The exact size of an Int
is implementation dependent but can be something like 28 bits, this type behaves like a Java primitive int
, e.g. it will overflow.
An Integer
is a type that can contain arbitrary-precision integers, like the Java BigInteger.
And a Float
is like a Java float
, using floating point arithmetic.
Just like numeric literals, many operators are also overloaded (using type classes), and can therefor be used with the different types. So the +
operator can work with both Int
s and Float
s.
In your case, since you didn't provide any type information, the interpreter will default to the Integer
type. This means that for the ^
operator, it will also choose the Integer
instance. Allowing for arbitrary-precision integer calculations.
It is a matter of how to encode numbers. The traditional way to do this is to encode numbers with a given number of bits, where you cannot have infinite accuracy. Haskell apparently do this with a variable number of bits for a number which is also fine, but usually mean that all math has do be done in software, as hardware acceleration is generally only available for finite accuracy.
Java has the notion of "Primitive Data Types" (which are the types that the processor supports) and those are different from all other Classes.
In Haskell, Int
is a type like all other types, and therefore it was easily made a member of the Num
and Integral
typeclasses used in (^)
("(^) :: (Num a, Integral b) => a -> b -> a"
). Another member of those typeclasses is Integer
, which supports integers of all sizes (as long as you have enough memory for their digits).
In Java, you can use many "Big Numbers" libraries, but the operations for them won't use the infix operators you are used to, because those are only for "Primitive Types" in Java.