Программирование на Java

       

Дробные типы


Дробные типы – это float и double. Их длина - 4 и 8 байт, соответственно. Оба типа знаковые. Ниже в таблице сведены их характеристики:

Таблица 4.2. Дробные типы данных.

Название типаДлина (байты)Область значений

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.


      Содержание раздела