kshnkvn
@kshnkvn
yay ✌️ t.me/kshnkvn

Почему не вычисляется значение типа Long?

import java.util.Scanner;

public class LightSpeed {
    public static void main(String[] args) {
        int lightSpeed = 300000;
        long calculatedDistance;
        int runningTime;
        System.out.print("Input days: ");
        Scanner input = new Scanner(System.in);
        runningTime = input.nextInt();
        calculatedDistance = runningTime * 86400 * lightSpeed;
        System.out.println("Light distance: " + calculatedDistance + " km.");
    }
}

Если тип переменной runningTime Int, то при вводе числа больше 14 произойдет ошибка, т.к. ответ выйдет за диапазон значений Int, но если изменить на Long, то проблемы не будет.
Вопрос: в строке calculatedDistance = runningTime * 86400 * lightSpeed; разве выполнение будет идти не по порядку? То-есть: присвоение в переменную calculatedDistance значение переменной runningTime, умножение числа на 86400 и умножение на lightSpeed? Такое ощущение, что операция происходит с переменной runningTime и только потом результат присваивается calculatedDistance.
Как происходят подобные математические операции? Они выполняются в переменной, а потом возвращается ей изначальное значение, или они выполняются в буфере? Тогда почему буфер имеет значение Int?
  • Вопрос задан
  • 91 просмотр
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Куратор тега Java
Седой и строгий
Есть две взаимосвязанных причины такого поведения. Во-первых, спецификация языка, стандартизирующая факт того, что перемножение int'ов даёт int:

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:
  1. If any operand is of a reference type, it is subjected to unboxing conversion (§5.1.8).
  2. Widening primitive conversion (§5.1.2) is applied to convert either or both operands as specified by the following rules:
    • If either operand is of type double, the other is converted to double.
    • Otherwise, if either operand is of type float, the other is converted to float.
    • Otherwise, if either operand is of type long, the other is converted to long.
    • Otherwise, both operands are converted to type int.


After the conversion(s), if any, value set conversion (§5.1.13) is then applied to each operand.

Binary numeric promotion is performed on the operands of certain operators:
  • The multiplicative operators *, /, and % (§15.17)
  • ...

Во-вторых, принципы работы стековых виртуальных машин. Вот так выглядит байткод метода main:
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=6, args_size=1
         0: ldc           #2   // int 300000
         2: istore_1
         3: getstatic     #3   // Field java/lang/System.out:Ljava/io/PrintStream;
         6: ldc           #4   // String Input days:
         8: invokevirtual #5   // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        11: new           #6   // class java/util/Scanner
        14: dup
        15: getstatic     #7   // Field java/lang/System.in:Ljava/io/InputStream;
        18: invokespecial #8   // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
        21: astore        5
        23: aload         5
        25: invokevirtual #9   // Method java/util/Scanner.nextInt:()I
        28: istore        4
        30: iload         4
        32: ldc           #10  // int 86400
        34: imul
        35: iload_1
        36: imul
        37: i2l
        38: lstore_2
        39: getstatic     #3   // Field java/lang/System.out:Ljava/io/PrintStream;
        42: new           #11  // class java/lang/StringBuilder
        45: dup
        46: invokespecial #12  // Method java/lang/StringBuilder."<init>":()V
        49: ldc           #13  // String Light distance:
        51: invokevirtual #14  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        54: lload_2
        55: invokevirtual #15  // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
        58: ldc           #16  // String  km.
        60: invokevirtual #14  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        63: invokevirtual #17  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        66: invokevirtual #18  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        69: return
     LocalVariableTable:
       Start  Length  Slot  Name   Signature
           0      70     0  args   [Ljava/lang/String;
           3      67     1 lightSpeed   I
          39      31     2 calculatedDistance   J
          30      40     4 runningTime   I
          23      47     5 input   Ljava/util/Scanner;

Интересны смещения с 30-го по 38-е. В стек загружаются два целых числа, перемножаются операцией imul, в результате которой с вершины стека убираются исходные два числа и помещается результат их перемножения, загружается ещё одно, ещё раз перемножаются, потом значение на вершине стека приводится к long операцией i2l и сохраняется в переменную calculatedDistance. Естественно, если результат работы imul окажется слишком большим, то произойдёт переполнение.

Исправить это легко, достаточно изменить тип константы:
calculatedDistance = runningTime * 86400L * lightSpeed;

Тогда компилятор выберет другие опкоды, оперирующие long'ами:
30: iload         4
32: i2l
33: ldc2_w        #10  // long 86400l
36: lmul
37: iload_1
38: i2l
39: lmul
40: lstore_2
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы