Почему эти две операции умножения дают разные результаты?

Зачем мне нужно добавить букву "L", чтобы получить правильное значение? И какова другая ценность?

long oneYearWithL = 1000*60*60*24*365L;
long oneYearWithoutL = 1000*60*60*24*365;
System.out.println(oneYearWithL);//gives correct calculation result : 31536000000
System.out.println(oneYearWithoutL)//gives incorrect calculation result: 1471228928

Ответ 1

long oneYearWithL = 1000*60*60*24*365L;
long oneYearWithoutL = 1000*60*60*24*365;

Ваше первое значение на самом деле длинное (так как 365L - это long, а 1000*60*60*24 - integer, поэтому результат значения multiplying a long со значением integer равен a long.

Но второе значение является целым числом (так как вы mulitplying значение integer только с значением integer. Таким образом, результат будет целым числом 32-bit. Теперь результат, полученный для этого multiplication, находится вне действительный диапазон integer.Таким образом, перед тем, как назначить эту переменную, она усекается, чтобы поместиться в допустимый целочисленный диапазон.

Посмотрите на следующий оператор печати: -

System.out.println(1000*60*60*24*365L);
System.out.println(1000*60*60*24*365);
System.out.println(Integer.MAX_VALUE);

При запуске вышеуказанного кода: -

Результат: -

31536000000
1471228928
2147483647

Итак, вы можете видеть разницу.

011101010111101100010010110000000000 -- Binary equivalent of 1000*60*60*24*365L
    01111111111111111111111111111111 -- Binary equivalent of Integer.MAX_VALUE

Итак, если вы не добавите это L в конце своего номера, 4 самых значимых бит будут удалены из первой двоичной строки.

Итак, строка становится...

(0111)01010111101100010010110000000000 -- Remove the most significant bits..
      01010111101100010010110000000000 -- Binary equivalent of 1471228928

(который вы получаете как вывод)


ОБНОВЛЕНИЕ: - Из приведенного выше объяснения вы также можете понять, что даже в первом назначении, если результат вашего multiplication of integers до его умножения с помощью 365L выходит за пределы диапазона, то снова он будет усечен, чтобы соответствовать целочисленный диапазон или преобразовать в 2 complement representation, если требуется, и тогда только он будет умножен на long value - 365L.

Например, например:

long thirtyYearWithL = 1000*60*60*24*30*365L;

В приведенном выше примере рассмотрим первую часть - 1000*60*60*24*30. Результатом этого умножения является: - 2592000000. Теперь давайте посмотрим, как он представлен в binary equivalent: -

2592000000 = 10011010011111101100100000000000  -- MSB is `1`, a negative value
             01100101100000010011100000000001  -- 2 complement representation

Десятичное представление представления 2 complement 1702967297. Таким образом, 2592000000 преобразуется в -1702967297, прежде чем умножается на 365L. Теперь, поскольку это значение соответствует integer range, которое: - [-2147483648 to 2147483647], поэтому оно не будет усекаться дальше.

Итак, фактический результат будет: -

long thirtyYearWithL = 1000*60*60*24*30*365L;
                     = 2592000000 * 365L;
                     = -1702967297 * 365L = -621583063040

Итак, все эти вещи просто рассматривают фактический type конечного результата при применении арифметической операции. И эта проверка выполняется для каждого временного результата операций, перемещающихся из left to right (с учетом операторов с ассоциативностью left-to-right). Если какой-либо временной результат окажется вне допустимого диапазона, то он преобразуется соответствующим образом, чтобы соответствовать требуемому диапазону, прежде чем двигаться вперед при следующей операции.


ОБНОВЛЕНИЕ 2: -

Итак, вместо: -

long thirtyYearWithL = 1000*60*60*24*30*365L;

если вы переместите свой 365L в начале, тогда вы получите правильный результат: -

long thirtyYearWithL = 365L*1000*60*60*24*30; // will give you correct result

Потому что теперь ваш результат temporary будет иметь тип long и способен удерживать это значение.

Ответ 2

Без L ваш расчет выполняется как 32-битное значение. Если вы выражаете значения как шестнадцатеричные, меньшее значение - это только младшие 4 байта большего значения.

Java по умолчанию имеет 32-битный целочисленный тип. L, для Long - 64 бит. Помещая L после 365, вы сообщаете компилятору относиться к 365 как к значению long. Когда умножаются значения 32 и 64 бит, компилятор увеличивает значение 32 бит до 64 бит, так что ваши промежуточные результаты при оценке вашего выражения сохраняют полный 64-битный диапазон.

См. Примитивные типы и значения в Спецификация языка Java.

Поведение умножения явно определено в https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.17.1 следующим образом:

15.17.1 Оператор умножения *

Двоичный оператор * выполняет умножение, производя произведение своих операндов. Умножение является коммутативной операцией, если выражения операнда не имеют побочных эффектов. Хотя целочисленное умножение ассоциативно, когда операнды все одного типа, умножение с плавающей запятой не является ассоциативным. Если целочисленное умножение переполняется, то результатом являются младшие биты математического произведения, представленные в некотором достаточно большом формате с двумя дополнениями. В результате, если происходит переполнение, знак результата может быть не таким, как знак математического произведения двух значений операнда.

Результат умножения с плавающей запятой определяется правилами арифметики IEEE 754:

  • Если либо операнд NaN, то результат NaN.
  • Если результат не NaN, знак результата положителен, если оба операнда имеют один и тот же знак и отрицательный, если операнды имеют разные знаки.
  • Умножение бесконечности на нуль приводит к NaN.
  • Умножение бесконечности на конечное значение приводит к отмеченной бесконечности. Знак определяется указанным выше правилом.
  • В остальных случаях, когда не задействована ни бесконечность, ни NaN, вычисляется точный математический продукт. Затем выбирается набор значений с плавающей запятой:
    • Если выражение умножения FP-строго (§15.4):
      • Если тип выражения умножения равен float, тогда необходимо выбрать значение с плавающей запятой.
      • Если тип выражения умножения double, тогда необходимо выбрать набор двойных значений.
    • Если выражение умножения не является FP-строгим:
      • Если тип выражения умножения float, то либо поплавковое значение, либо набор значений с расширением float-extended-exponent могут быть выбраны по прихоти реализации.
      • Если тип выражения умножения равен double, то может быть выбран либо набор двойного значения, либо набор значений с двойным расширенным экспонентом по прихоти реализации.

Затем значение должно быть выбрано из выбранного значения, заданного для представления продукта.

Если величина продукта слишком велика для представления, мы говорим, что операция переполняется; результатом является бесконечность соответствующего знака.

В противном случае продукт округляется до ближайшего значения в выбранном значении, установленном с использованием режима IEEE 754, близкого к ближайшему. Язык программирования Java требует поддержки постепенного перетока, как определено в IEEE 754 (§4.2.4).

Несмотря на то, что может произойти переполнение, недополнение или потеря информации, оценка оператора умножения * никогда не генерирует исключение во время выполнения.