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

Я пытаюсь написать код, чтобы определить, когда количество миллисекунд, прошедших с начала 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 в конце.

3 ответов


причина, по которой это имеет значение, заключается в том, что число научной нотации 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 занимает два шага:

  1. в первом шаг, число с плавающей запятой преобразуется либо в длинное, если T длинное

(snip)

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

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

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

(жирным выделено мной)

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

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

но вместо того, чтобы полагаться на добавление в конечном итоге уступая Long.MAX_VALUE, С Java 1.8+ вы можете явно проверить переполнение с помощью Math.addExact.

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

Броски:

ArithmeticException - если результат переполняет долго


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

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

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


@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 перевернулся.