为什么这两段相似的代码会产生不同的结果?

7

我一直在尝试使用Python作为初学者进行实验,过去几个小时写了一个递归函数,用Python和Java分别返回recurse(x)作为x!来比较这两种语言。两段代码完全相同,但由于某种原因,Python可以工作,而Java不行。在Python中,我写了以下内容:

x = int(raw_input("Enter: "))

def recurse(num):
    if num != 0:
        num = num * recurse(num-1)
    else:
        return 1

    return num 

print recurse(x)

变量num会被乘以num-1直到达到0,然后输出结果。在Java中,代码非常相似,只是稍微长一些:

public class Default {
    static Scanner input = new Scanner(System.in);
    public static void main(String[] args){

            System.out.print("Enter: ");
            int x = input.nextInt();
            System.out.print(recurse(x));


}

    public static int recurse(int num){

    if(num != 0){
    num = num * recurse(num - 1);
    } else {
        return 1;
    }

    return num;

}

如果我输入25,Python代码返回1.5511x10E25,这是正确的答案,但Java代码返回2,076,180,480,这不是正确的答案,我不确定为什么。两种代码都采用相同的过程:
- 检查num是否为零 - 如果num不为零 - num = num乘以num - 1的递归 - 如果num为零 - 返回1,结束该递归调用堆栈,并导致每个返回的num开始相乘 - 返回num
Python中没有括号;我认为这会改变一些东西,所以我从Java代码中删除了括号,但它并没有改变。将布尔值(num != 0)更改为(num>0)也没有改变任何内容。在else中添加if语句提供了更多上下文,但值仍然相同。
在每个点打印num的值可以了解函数如何出错:
Python:
1
2
6
24
120
720
5040
40320
362880
3628800
39916800
479001600
6227020800
87178291200
1307674368000
20922789888000
355687428096000
6402373705728000
121645100408832000
2432902008176640000
51090942171709440000
1124000727777607680000
25852016738884976640000
620448401733239439360000
15511210043330985984000000
15511210043330985984000000

一个稳步增长的趋势。在Java中:
 1
 2 
 6  
 24
 120 
 720
 5040
 40320
 362880
 3628800
 39916800
 479001600
 1932053504
 1278945280
 2004310016
 2004189184
-288522240
-898433024
 109641728
-2102132736 
-1195114496
-522715136
 862453760
-775946240
 2076180480
 2076180480

并非稳定增加。实际上,num返回了负数,就好像该函数返回了负数一样,即使num不应该低于零。

Python和Java代码都在执行相同的过程,但它们返回的值却大相径庭。为什么会发生这种情况?


6
整数溢出... - Mysticial
3
Python可以自动将整数类型提升为长整型,而Java不行。 - jamylak
这是一个非常好的问题,因为所有在这里回答问题的人都立刻理解了问题,但这是一些你不能真正搜索到的东西,除非你已经知道这个问题叫什么。 - Karl Knechtel
6个回答

11

两个词——整数溢出

虽然我不是 Python 的专家,但我认为它可以根据需要扩展整数类型的大小。

然而,在 Java 中,int 类型的大小是固定的,即 32 位,由于 int 是有符号的,我们实际上只有 31 位来表示正数。一旦你分配的数字超过了最大值,它就会发生溢出(也就是说,没有位置来表示整个数字)。

虽然在 C 语言中这种情况的行为是未定义的,但在 Java 中它是明确定义的,并且它只取结果的最后 4 个字节。

例如:

System.out.println(Integer.MAX_VALUE + 1);
// Integer.MAX_VALUE = 0x7fffffff

结果为:

-2147483648
// 0x7fffffff + 1 = 0x800000000

编辑

为了更加清晰,这里提供另一个例子。以下代码:

int a = 0x12345678;
int b = 0x12345678;
System.out.println("a*b as int multiplication (overflown) [DECIMAL]: " + (a*b));
System.out.println("a*b as int multiplication (overflown) [HEX]: 0x" + Integer.toHexString(a*b));
System.out.println("a*b as long multiplication (overflown) [DECIMAL]: " + ((long)a*b));
System.out.println("a*b as long multiplication (overflown) [HEX]: 0x" + Long.toHexString((long)a*b));

输出:

a*b as int multiplication (overflown) [DECIMAL]: 502585408
a*b as int multiplication (overflown) [HEX]: 0x1df4d840
a*b as long multiplication (overflown) [DECIMAL]: 93281312872650816
a*b as long multiplication (overflown) [HEX]: 0x14b66dc1df4d840

你可以看到第二个输出是4个输出中最少的4个字节。


1
有趣。你能提供上下文吗? - Zolani13
1
在Java中,整数由固定数量的字节表示,当这些字节所表示的最大十进制数达到时,它会循环到负值(最终回到0)。而Python则会动态增加字节数。 - Istinra
1
Java中的整数只有32位。如果您使用长整型,可能会更好。它们使用64位编码值。两者最左边的位都是符号位。 - Phil Freihofner
3
@Zolani13,它的工作方式是相同的......直到某个限制(64位,当值达到2^63 = 9,223,372,036,854,775,807时会发生溢出)。 - kaveman
@Zolani13 - 因为它超过了两个单词 - 我再次进行了编辑 - 你可能会觉得它很有趣。 - MByD
显示剩余5条评论

2
与Java不同,Python内置了对无限精度的长整数的支持。在Java中,整数被限制为32位并且会溢出

1

正如其他人所写,您会遇到溢出问题;这些数字根本无法适应Java的数据类型表示。Python具有内置的bignum功能,而Java没有。

尝试一些较小的值,您会发现您的Java代码可以正常工作。


1

Java的int范围

int是4个字节,有符号(二进制补码)。范围为-2,147,483,648到2,147,483,647。像所有数字类型一样,int可以转换为其他数字类型(byte、short、long、float、double)。当进行有损转换时(例如从int到byte),转换是在较小类型的长度模下完成的。

这里int的范围受限。


0
问题非常简单... 因为在Java中,整数的最大限制是2147483647,您可以通过System.out.println(Integer.MAX_VALUE);打印它, 最小值是System.out.println(Integer.MIN_VALUE);

0

由于在Java版本中,您将数字存储为int,我相信它是32位的。考虑在二进制中使用两位可以存储的最大(无符号)数字:11,这是十进制中的数字3。可以用四位二进制表示的最大数字是1111,这是十进制中的数字15。一个32位(带符号)数字无法存储比2,147,483,647更大的任何数字。当您尝试存储比此更大的数字时,它会突然回到负数并从负数开始计数。这称为溢出。

如果您想尝试存储更大的数字,请尝试使用long。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接