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