Подтвердить что ты не робот

Почему запись кода в научной нотации имеет значение в этом коде?

Я пытаюсь написать код, чтобы определить, когда количество миллисекунд с начала 1970 года будет превышать пропускную способность. Появится следующий код:

public class Y2K {
    public static void main(String[] args) {
        int year = 1970;
        long cumSeconds = 0;

        while (cumSeconds < Long.MAX_VALUE) {
            // 31557600000 is the number of milliseconds in a year
            cumSeconds += 3.15576E+10;
            year++;
        }
        System.out.println(year);
    }
}

Этот код выполняется в течение нескольких секунд и печатает 292272992. Если вместо использования научной нотации я пишу cumSeconds как 31558000000L, программа, кажется, занимает "навсегда" для запуска (я просто ударил паузу через 10 минут или около того). Также обратите внимание, что запись cumSeconds в научной нотации не требует указания числа long с L или l в конце.

4b9b3361

Ответ 1

Причина, по которой это имеет значение, состоит в том, что номер научной нотации 3.1558E+10 является литералом double, тогда как литерал 31558000000L является, конечно, литералом long.

Это делает разницу в операторе +=.

Выражение составного присваивания формы E1 op = E2 эквивалентно E1 = (T) ((E1) op (E2)), где T - тип E1, за исключением того, что E1 оценивается только один раз.

В принципе, long + = long дает длинный, но long + = double также дает длинный.

При добавлении double начальное значение cumSeconds расширяется до double, а затем происходит добавление. Результат претерпевает сужение примитивного преобразования до long.

Сужение преобразования числа с плавающей запятой интегральному типу T выполняется двумя шагами:

  • На первом этапе число с плавающей запятой преобразуется либо в long, если T long

(надрез)

  • В противном случае должен выполняться один из следующих двух случаев:

    • Значение должно быть слишком маленьким (отрицательное значение большой величины или отрицательной бесконечности), а результат первого шага - наименьшее представимое значение типа int или long.

    • Значение должно быть слишком большим (положительное значение большой величины или положительной бесконечности), а результатом первого шага является наибольшее представимое значение типа int или long.

(смелый акцент мой)

Результат в конечном итоге слишком большой, чтобы быть представленным в long, поэтому результат сужается до Long.MAX_VALUE, а цикл while заканчивается.

Однако, когда вы используете литерал long, вы постоянно добавляете четное значение к четному значению, которое в конечном итоге будет переполняться. Это не устанавливает значение Long.MAX_VALUE, которое является нечетным, поэтому цикл бесконечен.

Ответ 2

Главное наблюдение заключается в том, что cumSeconds < Long.MAX_VALUE, где cumSeconds является long, может быть только false, если cumSeconds точно Long.MAX_VALUE.

Если вы выполняете вычисления с длинными номерами, для достижения этого значения требуется довольно некоторое время (если оно когда-либо достигнуто), потому что длительная арифметика обходит вокруг, когда вы покидаете диапазон номеров.

Выполнение арифметики с двойными номерами даст максимальное значение, если двойное значение достаточно велико.

Ответ 3

@rgettman уже подробно рассказал о закругляющей гимнастике, которая имеет место, когда вы используете double вместо long. Но там больше.

Когда вы повторно добавляете большое число в long, вы в конечном итоге получите отрицательный результат. Например, Long.MAX_VALUE + 1L = Long.MIN_VALUE. Когда это произойдет, вы просто повторите этот процесс на неопределенный срок.

Итак, если вы изменили свой код на:

    while (cumSeconds >= 0L) {
        // 31557600000 is the number of milliseconds in a year
        cumSeconds += 31557600000L;

вы поймете, что все идет отрицательно, потому что cumSeconds перевернулся.