Добрый день! Начал проходить курс "Программирование на Java". Как я понимаю,курс создавался приблизительно в 2015 году. Не потерял ли данный курс свою актуальность? Стоит ли проходить его в 2023 году, или же лучше найти что-то более новое? |
Типы данных
Дробные типы
Дробные типы – это float и double . Их длина - 4 и 8 байт, соответственно. Оба типа знаковые. Ниже в таблице сведены их характеристики:
Название типа | Длина (байты) | Область значений |
---|---|---|
float | 4 | 3.40282347e+38f ; 1.40239846e-45f |
double | 8 | 1.79769313486231570e+308 ; 4.94065645841246544e-324 |
Для целочисленных типов область значений задавалась верхней и нижней границами, весьма близкими по модулю. Для дробных типов добавляется еще одно ограничение – насколько можно приблизиться к нулю, другими словами – каково наименьшее положительное ненулевое значение. Таким образом, нельзя задать литерал заведомо больший, чем позволяет соответствующий тип данных, это приведет к ошибке overflow. И нельзя задать литерал, значение которого по модулю слишком мало для данного типа, компилятор сгенерирует ошибку underflow.
// пример вызовет ошибку компиляции float f = 1e40f; // значение слишком велико, overflow double d = 1e-350; // значение слишком мало, underflow
Напомним, что если в конце литерала стоит буква F или f, то литерал рассматривается как значение типа float. По умолчанию дробный литерал имеет тип double, при желании это можно подчеркнуть буквой D или d.
Над дробными аргументами можно производить следующие операции:
- операции сравнения (возвращают булево значение)
- <, <=, >, >=
- ==, !=
- числовые операции (возвращают числовое значение)
- унарные операции + и -
- арифметические операции +, -, *, /, %
- операции инкремента и декремента (в префиксной и постфиксной форме): ++ и --
- оператор с условием ?:
- оператор приведения типов
- оператор конкатенации со строкой +
Практически все операторы действуют по тем же принципам, которые предусмотрены для целочисленных операторов (оператор деления с остатком % рассматривался в предыдущей лекции, а операторы ++ и -- также увеличивают или уменьшают значение переменной на единицу). Уточним лишь, что операторы сравнения корректно работают и в случае сравнения целочисленных значений с дробными. Таким образом, в основном необходимо рассмотреть вопросы переполнения и преобразования типов при вычислениях.
Для дробных вычислений появляется уже два типа переполнения – overflow и underflow. Тем не менее, Java и здесь никак не сообщает о возникновении подобных ситуаций. Нет ни ошибок, ни других способов обнаружить их. Более того, даже деление на ноль не приводит к некорректной ситуации. А значит, дробные вычисления вообще не порождают никаких ошибок.
Такая свобода связана с наличием специальных значений дробного типа. Они определяются спецификацией IEEE 754 и уже перечислялись в лекции 3:
- положительная и отрицательная бесконечности (positive/negative infinity);
- значение "не число", Not-a-Number, сокращенно NaN ;
- положительный и отрицательный нули.
Все эти значения представлены как для типа float, так и для double.
Положительную и отрицательную бесконечности можно получить следующим образом:
1f/0f // положительная бесконечность, // тип float -1d/0d // отрицательная бесконечность, // тип double
Также в классах Float и Double определены константы POSITIVE_INFINITY и NEGATIVE_INFINITY. Как видно из примера, такие величины получаются при делении конечных величин на ноль.
Значение NaN можно получить, например, в результате следующих действий:
0.0/0.0 // деление ноль на ноль (1.0/0.0)*0.0 // умножение бесконечности на ноль
Эта величина также представлена константами NaN в классах Float и Double.
Величины положительный и отрицательный ноль записываются очевидным образом:
0.0 // дробный литерал со значением // положительного нуля +0.0 // унарная операция +, ее значение - // положительный ноль -0.0 // унарная операция -, ее значение - // отрицательный ноль
Все дробные значения строго упорядочены. Отрицательная бесконечность меньше любого другого дробного значения, положительная – больше. Значения +0.0 и -0.0 считаются равными, то есть выражение 0.0==-0.0 истинно, а 0.0>-0.0 – ложно. Однако другие операторы различают их, например, выражение 1.0/0.0 дает положительную бесконечность, а 1.0/-0.0 – отрицательную.
Единственное исключение - значение NaN. Если хотя бы один из аргументов операции сравнения равняется NaN, то результат заведомо будет false (для оператора != соответственно всегда true ). Таким образом, единственное значение x, при котором выражение x!=x истинно,– именно NaN.
Возвращаемся к вопросу переполнения в числовых операциях. Если получаемое значение слишком велико по модулю ( overflow ), то результатом будет бесконечность соответствующего знака.
print(1e20f*1e20f); print(-1e200*1e200);
В результате получаем:
Infinity -Infinity
Если результат, напротив, получается слишком мал ( underflow ), то он просто округляется до нуля. Так же поступают и в том случае, когда количество десятичных знаков превышает допустимое:
print(1e-40f/1e10f); // underflow для float print(-1e-300/1e100); // underflow для double float f=1e-6f; print(f); f+=0.002f; print(f); f+=3; print(f); f+=4000; print(f);
Результатом будет:
0.0 -0.0 1.0E-6 0.002001 3.002001 4003.002
Как видно, в последней строке был утрачен 6-й разряд после десятичной точки.
Другой пример (из спецификации языка Java):
double d = 1e-305 * Math.PI; print(d); for (int i = 0; i < 4; i++) print(d /= 100000);
Результатом будет:
3.141592653589793E-305 3.1415926535898E-310 3.141592653E-315 3.142E-320 0.0
Таким образом, как и для целочисленных значений, явное выписывание в коде литералов, которые слишком велики ( overflow ) или слишком малы ( underflow ) для используемых типов, приводит к ошибке компиляции (см. лекцию 3). Если же переполнение возникает в результате выполнения операции, то возвращается одно из специальных значений.
Теперь перейдем к преобразованию типов. Если хотя бы один аргумент имеет тип double, то значения всех аргументов приводятся к этому типу и результат операции также будет иметь тип double. Вычисление будет произведено с точностью в 64 бита.
Если же аргументов типа double нет, а хотя бы один аргумент имеет тип float, то все аргументы приводятся к float, вычисление производится с точностью в 32 бита и результат имеет тип float.
Эти утверждения верны и в случае, если один из аргументов целочисленный. Если хотя бы один из аргументов имеет значение NaN, то и результатом операции будет NaN.
Еще раз рассмотрим простой пример:
print(1/2); print(1/2.);
Результатом будет:
0 0.5
Достаточно одного дробного аргумента, чтобы результат операции также имел дробный тип.
Более сложный пример:
int x=3; int y=5; print (x/y); print((double)x/y); print(1.0*x/y);
Результатом будет:
0 0.6 0.6
В первый раз оба аргумента были целыми, поэтому в результате получился ноль. Однако поскольку оба операнда представлены переменными, в этом примере нельзя просто поставить десятичную точку и таким образом перевести вычисления в дробный тип. Необходимо либо преобразовать один из аргументов (второй вывод на экран), либо вставить еще одну фиктивную операцию с дробным аргументом (последняя строка).
Приведение типов подробно рассматривается в другой лекции, однако обратим здесь внимание на несколько моментов.
Во-первых, при приведении дробных значений к целым типам дробная часть просто отбрасывается. Например, число 3.84 будет преобразовано в целое 3, а -3.84 превратится в -3. Для математического округления необходимо воспользоваться методом класса Math.round(…).
Во-вторых, при приведении значений int к типу float и при приведении значений типа long к типу float и double возможны потери точности, несмотря на то, что эти дробные типы вмещают гораздо большие числа, чем соответствующие целые. Рассмотрим следующий пример:
long l=111111111111L; float f = l; l = (long) f; print(l);
Результатом будет:
111111110656
Тип float не смог сохранить все значащие разряды, хотя преобразование от long к float произошло без специального оператора в отличие от обратного перехода.
Для каждого примитивного типа существуют специальные вспомогательные классы-обертки (wrapper classes). Для типов float и double это Float и Double. Эти классы содержат многие полезные методы для работы с дробными значениями. Например, преобразование из текста в число.
Кроме того, класс Math предоставляет большое количество методов для операций над дробными значениями, например, извлечение квадратного корня, возведение в любую степень, тригонометрические и другие. Также в этом классе определены константы PI и основание натурального логарифма E.
Булев тип
Булев тип представлен всего одним типом boolean, который может хранить всего два возможных значения – true и false . Величины именно этого типа получаются в результате операций сравнения.
Над булевыми аргументами можно производить следующие операции:
- операции сравнения (возвращают булево значение)
- ==, !=
- логические операции (возвращают булево значение)
- !
- &, |, ^
- &&, ||
- оператор с условием ?:
- оператор конкатенации со строкой +
Логические операторы && и || обсуждались в предыдущей лекции. В операторе с условием ?: первым аргументом может быть только значение типа boolean. Также допускается, чтобы второй и третий аргументы одновременно имели булев тип.
Операция конкатенации со строкой превращает булеву величину в текст "true" или "false" в зависимости от значения.
Только булевы выражения допускаются для управления потоком вычислений, например, в качестве критерия условного перехода if.
Никакое число не может быть интерпретировано как булево выражение. Если предполагается, что ненулевое значение эквивалентно истине (по правилам языка С), то необходимо записать x!=0. Ссылочные величины можно преобразовывать в boolean выражением ref!=null.