Поведение операции умножения Java - PullRequest
8 голосов
/ 04 февраля 2009

Я написал метод для преобразования заданного числа из дней в миллисекунды:

private long expireTimeInMilliseconds;
...
public void setExpireTimeInDays(int expireTimeInDays)
{
   expireTimeInMilliseconds = expireTimeInDays * 24 * 60 * 60 * 1000;
}

Мне было трудно понять, что я сделал не так. Теперь мой вопрос: Эта ошибка настолько очевидна?

Исправленный метод:

private long expireTimeInMilliseconds;
...
public void setExpireTimeInDays(int expireTimeInDays)
{
   expireTimeInMilliseconds = ((long) expireTimeInDays) * 24 * 60 * 60 * 1000;
}

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

Ответы [ 10 ]

8 голосов
/ 04 февраля 2009

Это очевидно? Я думаю, это зависит от того, как долго вы используете Java и сколько раз вам приходилось иметь дело с миллисекундами. Конечно, это должно быть хорошо на срок до 24 дней ...

Я думаю, что самый большой намек на то, что System.currentTimeMillis() возвращает long. Это хороший признак того, что количество миллисекунд может увеличиться. Тип переменной, которую вы устанавливаете, также должен быть хорошим советом.

Конечно, вы также должны знать, что если вы будете выполнять арифметические операции с целыми числами, результат будет int с циклическим переполнением при переполнении. Достаточно ли это очевидно или нет, можно обсуждать, но это будет довольно бессмысленное обсуждение. В C #, если вы включили проверку переполнения, вы бы нашли ошибку довольно быстро - но тогда не многие разработчики делают это (на самом деле, я не знаю, хотя, вероятно, должен).

7 голосов
/ 04 февраля 2009

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

3 голосов
/ 04 февраля 2009

Вам может быть интересно узнать, что это описано в «Java Puzzlers» Джошуа Блоха и Нила Гафтера.

alt text
(источник: javapuzzlers.com )

В этой книге вы найдете множество других подводных камней, ловушек и угловых случаев.

Я согласен с тем, кто оставил комментарий. Добавьте L к числу.

3 голосов
/ 04 февраля 2009

Ваша переменная операнда и литеральные числа имеют тип int. Тип данных int имеет максимальное значение 2 ^ 31 -1. Поэтому при таких больших числах тип данных int переполняется, что приводит к кажущемуся неправильному ответу.

В первом примере int присваивается значение long только при присвоении переменной, которое происходит после вычисления. Результатом расчета является int.

Во втором примере первый операнд приводится к длинному, в результате чего вычисление становится длинным. В этом случае результат расчета будет долгим, благодаря продвижению по службе. Тип данных long более чем достаточно велик для ваших расчетов.

2 голосов
/ 04 февраля 2009

Нет, это не очевидно.

Но, поверьте мне, после еще нескольких лет практики и исправления ошибок, подобных этой, вы очень разумно относитесь к целочисленным переполнениям и просто делаете правильные вещи, даже не задумываясь об этом.

Это то, что случилось со всеми. Определенно нет признаков плохой практики кода, невежества или около того.

1 голос
/ 05 февраля 2009

Есть некоторый инструмент статического анализа (findbugs), который найдет ошибки такого типа.

Числовая математика на компьютерах может быть сложной. Порядок операций может повлиять на точность и точность, чего вы не ожидаете. Математика даты также может быть удивительно хитрой. Часто лучше использовать подпрограммы Date / Calendar, а не пытаться делать математику самостоятельно, но эти подпрограммы не лучше всего разработаны в библиотеке классов java.

1 голос
/ 05 февраля 2009

Если вы используете FindBugs в своем коде, это обнаружит именно эту проблему. «ICAST: результат умножения целого числа на длинный». Пример FindBugs - именно то, что вы делаете; Подсчет дней в миллисекундах.

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

1 голос
/ 05 февраля 2009

Еще один способ написать это

public void setExpireTimeInDays(int expireTimeInDays)
{
   expireTimeInMilliseconds = (long) expireTimeInDays * 24 * 60 * 60 * 1000;
}

или

public void setExpireTimeInDays(int expireTimeInDays)
{
   expireTimeInMilliseconds = expireTimeInDays * 24L * 60 * 60 * 1000;
}
1 голос
/ 04 февраля 2009

Просто чтобы добавить к другим ответам, в прошлом мне было полезно определять константы (public static final long), такие как MILLISECS_DAY или MILLISECS_HOUR. Гораздо читабельнее и полезнее.

0 голосов
/ 04 февраля 2009

Я не пытаюсь оправдать свою ошибку, но было бы здорово, если бы java-компилятор был достаточно умен, чтобы выдавать int задолго до вычисления (после того, как вычисление назначается переменной типа long) 1001 *

Кстати, раньше я работал с C / C ++, и если бы это была программа на C, у меня была бы такая же проблема, но несколько лет назад я был более осторожен с подобными операциями.

В следующий раз я уделю больше внимания (или переключусь на python) ...: D

...