Java amount что значит

Пара слов о числах с плавающей точкой в Java


Несколько дней назад мне на глаза попался занимательный такой вопрос, касающийся того, каков будет результат выполнения данного кода:

Вопреки всем моим ожиданиям, ответ: 0.89999999999999991 в первом случае и 0.99999999999999989 во втором.
Для тех, кто хочет узнать почему, а так же еще несколько занимательных фактов про этот тип данных, милости просим.

В общем виде ответ на поставленный выше вопрос будет звучать примерно так: «Подобные ошибки связанны с внутренним двоичным (binary) представлением чисел. Подобно тому, как в десятичной системе нельзя точно представить результат деления 1/3, так и в двоичной системе невозможно точно представить 1/10. Если вам нужно исключить ошибки округления, следует использовать класс BigDecimal».

Существует важное различие между абстрактными вещественными числами, такими как π или 0.2, и типом данных double в Java. Во-первых, платонически-идеальное представление вещественных чисел является бесконечным, в то время как представление в Java ограничено числом бит. Однако точность вычислений является еще более насущной проблемой, чем ограничение на размер чисел. Еще больше «интригует» совершенно оригинальный способ округления чисел, но обо всем по порядку.

Начать, пожалуй, стоит с двоичного представления целых чисел. Этот абзац нам пригодится чуть позже. Итак. Самым простым вариантом представления целых чисел считается так называемый «Прямой код», в котором старший бит используется для записи знака числа (0 — положительное, 1 — отрицательное), а оставшиеся биты используются непосредственно для записи самого значения. Таким образом, число «-9» в восьмиразрядном представлении будет выглядеть как 10001001. Недостатком такого подхода считается наличие двух нулей («+0» и «-0») и усложнение арифметических операций с отрицательными числами. Другим вариантом, интересующим нас, является «Код со сдвигом», в котором, говоря простым языком, мы прибавляем к нашему числу некое константное для данного типа представления число, равное 2^(n-1), где n — число разрядов (бит). В нашем случае, пример с числом «-9» в восьмиразрядном представлении будет выглядеть так:
-9 + 2^(8-1) = -9 + 128 = 119. В двоичном виде получим 01110111. Этот вариант удобен тем, что ноль у нас всего один, однако при арифметических операциях необходимо будет учитывать смещение.

Читайте также:  Тенькать что это значит

Здесь стоит упомянуть вот о чем. Одной из заявленных целей языка Java является машинная независимость. Вычисления должны приводить к одинаковому результату, независимо от того, какая виртуальная машина их выполняет. Для арифметических вычислений над числами с плавающей точкой это неожиданно оказалось трудной задачей. Тип double использует для хранения числовых значений 64 бита, однако некоторые процессоры применяют 80-разрядные регистры с плавающей точкой. Эти регистры обеспечивают дополнительную точность на промежуточных этапах вычисления, т.е. промежуточный результат вычислений храниться в 80-разрядном регистре, после чего ответ округляется до 64 бит. Однако этот результат может оказаться иным, если в процессе всех вычислений используется 64-разрядный процессор. По этой причине в первоначальном описании JVM указывалось, что все промежуточные вычисления должны округляться. Это вызвало протест многих специалистов, поскольку подобное округление не только может привести к переполнению, но и сами вычисления происходят медленнее. Это привело к тому, что в JDK 1.2 появилась поддержка ключевого слова strictfp, гарантирующая воспроизводимость результатов всех вычислений, производимых внутри этого метода, класса или интерфейса (вернее его реализации). Иными словами, ключевое слово strictfp гарантирует, что на каждой платформе вычисления с плавающей точкой будут вести себя одинаково и с определенной точностью, даже если некоторые платформы могут производить вычисления с большей точностью. Интересно, что для процессоров семейства x86 модуль операций с плавающей точкой был выделен в отдельную микросхему, называемую математическим сопроцессором (floating point unit (FPU)). Начиная с процессоров Pentium модели MMX модуль операций с плавающей точкой интегрирован в центральный процессор. Подробнее.

Далее. Стандарт IEEE 754 говорит нам, что представление действительных чисел должно записываться в экспоненциальном виде. Это значит, что часть битов кодирует собой так называемую мантиссу числа, другая часть — показатель порядка (степени), и ещё один бит используется для указания знака числа (0 — если число положительное, 1 — если число отрицательное). Математически это записывается так:
(-1)^s × M × 2^E, где s — знак, M — мантисса, а E — экспонента. Экспонента записывается со сдвигом, который можно получить по формуле, приведенной выше.

Читайте также:  Что значит вывод генератора

Что такое мантисса и экспонента? Мантисса – это целое число фиксированной длины, которое представляет старшие разряды действительного числа. Допустим наша мантисса состоит из четырех бит (|M|=4). Возьмем, например, число «9», которое в двоичной системе будет равно 1001.
Экспонента (ее еще называют «порядком» или «показателем степени») – это степень базы (двойки) старшего разряда. Можно рассматривать ее как количество цифр перед точкой, отделяющей дробную часть числа. Если экспонента переменная, записываемая в регистр и неизвестная при компиляции, то число называют «числом с плавающей точкой». Если экспонента известна заранее, то число называют «числом с фиксированной точкой». Числа с фиксированной точкой могут записываться в обыкновенные целочисленные переменные (регистры) путем сохранения только мантиссы. В случае же записи чисел с плавающей точкой, записываются и мантиса и экспонента в так называемом стандартном виде, например «1.001e+3». Сразу видно, что мантисса состоит из четырех знаков, а экспонента равна трем.

Допустим мы хотим получить дробное число, используя те же 3 бита мантиссы. Мы можем это сделать, если возьмем, скажем, E=1. Тогда наше число будет равно

1.001e+1 = 1×2^2 + 0×2^1 + 0×2^0 + 1×2^(-1) = 4 + 0,5 = 4,5

Одной из проблем такого подхода может стать различное представление одного и того же числа в рамках одной длины мантиссы. Нашу «9-ку», при длине мантиссы равной 5, можно представить и как 1.00100e+3 и как 0.10010e+4 и как 0.01001e+5. Это не удобно для оборудования, т.к. нужно учитывать множественность представления при сравнении чисел и при выполнении над ними арифметических операций. Кроме того, это не экономично, поскольку число представлений — конечное, а повторения уменьшают множество чисел, которые вообще могут быть представлены. Однако тут есть маленькая хитрость. Оказывается, что для вычисления значения первого бита можно использовать экспоненту. Если все биты экспоненты равны 0, то первый бит мантиссы также считается равным нулю, в противном случае он равен единице. Числа с плавающей точкой, в которых первый бит мантиссы равен единице, являются нормализованными. Числа с плавающей точкой, первый бит мантиссы в которых равен нулю, называются денормализованными. С их помощью можно представлять значительно меньшие величины. Поскольку первый бит всегда может быть вычислен, нет необходимости хранить его явным образом. Это экономит один бит, так как неявную единицу не нужно хранить в памяти, и обеспечивает уникальность представления числа. В нашем примере с «9» нормализованным представлением будет 1.00100e+3, а мантисса будет храниться в памяти как «00100», т.к. старшая единица подразумевается неявно. Проблемой такого подхода является невозможность представления нуля, о которой я скажу чуть позже. Подробнее об этом и многом другом можно почитать тут и тут.

К слову, в JDK 1.5 допустимо задавать числа с плавающей точкой в шестнадцатеричном формате. Например, 0.125 можно представить как 0x1.0p-3. В шестнадцатеричной записи для указания экспоненты используется знак «p» вместо «е».

Вещи, о которых стоит помнить, работая с Double:

  1. Целочисленное деление на 0 генерирует исключение, в то время как результатом деления на 0 чисел с плавающей точкой является бесконечность (или NaN в случае деления 0.0/0). Кстати мне было интересно узнать, что разработчики JVM, согласно все тому же стандарту IEEE 754 ввели также и значения Double.NEGATIVE_INFINITY и Double.POSITIVE_INFINITY, равные -1.0 / 0.0 и 1.0 / 0.0 соответственно.
  2. Double.MIN_VALUE на самом деле не самое маленькое число, которое можно записать в double. Помните, мы говорили о том, что согласно стандарту IEEE 754, старшая единица мантиссы указывается неявно? Так вот. Как уже было оговорено выше, в нормализованной форме числа с плавающей точкой невозможно представить ноль, поскольку нет такой степени двойки, которая равнялась бы нулю. И разработчики JVM специально для решения этой проблемы ввели переменную Double.MIN_VALUE, которая, по сути, является максимально близким значением к нулю. Самым маленьким значением, которое вы можете сохранить в double является «-Double.MAX_VALUE».
  3. Развивая предыдущую тему, можно привести еще один интересный пример, показывающий нам, что не все так очевидно, как может показаться на первый взгляд. Double.MAX_VALUE возвращает нам 1.7976931348623157E308, но что будет если мы преобразуем строку, содержащую число с плавающей запятой в double?

Оказывается, между Double.MAX_VALUE и Double.POSITIVE_INFINITY есть еще некоторые значения, которые при вычислении округляются в одну или другую сторону. Тут стоит остановиться подробнее.

Множество вещественных чисел является бесконечно плотным (dense). Не существует такого понятия, как следующее вещественное число. Для любых двух вещественных чисел существует вещественное число в промежутке между ними. Это свойство не выполняется для чисел с плавающей точкой. Для каждого числа типа float или double существует следующее число. Кроме того, существует минимальное конечное расстояние между двумя последовательными числами типа float или double. Метод Math.nextUp() возвращает следующее число с плавающей точкой, превышающее заданный параметр. Например, данный код печатает все числа типа float между 1.0 и 2.0 включительно.

Оказывается, что в промежутке от 1.0 до 2.0 включительно лежит ровно 8,388,609 чисел типа float. Это немало, но намного меньше, чем бесконечное множество вещественных чисел, которые находятся в том же диапазоне. Каждая пара последовательных чисел типа float находится на расстоянии примерно 0.0000001 друг от друга. Это расстояние называется единицей наименьшей точности (unit of least precision – ULP). Для типа double ситуация совершенно идентичная, за исключением того факта, что кол-во чисел после запятой значительно выше.

Пожалуй, на этом все. Желающим «копнуть поглубже» может пригодится следующий код:

Спасибо всем осилившим. Буду рад конструктивной критике и дополнениям.

Источник

Учебное задание: подсчёт денежных единиц

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

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

Вот шаги по разработке программы:

  1. Попросите пользователя ввести сумму в виде десятичного числа, например, 11.56.
  2. Преобразуйте количество (например, 11.56) в центы (1156).
  3. Разделите центы на 100, чтобы найти количество долларов. Получите оставшиеся центы, используя целый остаток от деления на 100 центов.
  4. Разделите оставшиеся центы на 25, чтобы найти количество четвертаков. Получите оставшиеся центы, используя остаток целочисленного деления на 25.
  5. Разделите оставшиеся центы на 10, чтобы найти количество десятицентовиков. Получите оставшиеся центы, используя остаток от деления на 10.
  6. Разделите оставшиеся центы на 5, чтобы найти количество пятаков. Получите оставшиеся центы, используя оставшиеся от целочисленного деления на 5 центы.
  7. Остальные центы — это пенни.
  8. Отобразите результат.

Переменная amount хранит сумму, введенную с консоли (строка 11). Эта переменная не изменяется, так как сумма должна быть использована в конце программы для отображения результатов. Программа вводит переменную remainingAmount (строка 13) для сохранения изменения оставшейся суммы.

Переменная amount представляет собой десятичную цифру double, представляющую доллары и центы. Она преобразуется в int переменную remainingAmount, которая представляет все центы. Например, если amount равна 11,56, то первоначальная remainingAmount равна 1156. Оператор деления дает целочисленную часть деления, поэтому 1156/100 равно 11. Оператор остатка от деления получает остаток от деления, поэтому 1156 % 100 составляет 56.

Программа извлекает максимальное количество долларов из оставшейся суммы и получает новую оставшуюся сумму в переменной remainingAmount (строки 16-17). Затем она извлекает максимальное количество четвертаков из remainingAmount и получает новый remainingAmount (строки 20-21). Продолжая тот же процесс, программа находит максимальное количество десячков, пятаков и пенни в оставшейся сумме.

Одной из серьезных проблем с этим примером является возможная потеря точности при преобразовании double amount в int remainingAmount. Это может привести к неточному результату. Если вы попытаетесь ввести сумму 10.03, 10.03 * 100 станет 1002.9999999999999. Вы обнаружите, что программа отображает 10 долларов и 2 пенни. Чтобы устранить проблему, введите сумму как целое значение, представляющее центы (смотрите упражнение 22).

Источник

Как использовать BigDecimal в Java

Привет! На сегодняшней лекции мы поговорим о больших числах. Нет, о ДЕЙСТВИТЕЛЬНО БОЛЬШИХ. Ранее мы не раз встречали таблицу диапазонов значений для примитивных типов данных. Выглядит она так:

Примитивный тип Размер в памяти Диапазон значений
byte 8 бит от -128 до 127
short 16 бит до -32768 до 32767
char 16 бит от 0 до 65536
int 32 бита от -2147483648 до 2147483647
long 64 бита от -9223372036854775808 до 9223372036854775807
float 32 бита от (2 в степени -149) до ((2-2 в степени -23)*2 в степени 127)
double 64 бита от (-2 в степени 63) до ((2 в степени 63) — 1)
boolean 8 (при использовании в массивах), 32 (при использовании не в массивах) true или false

Если мы говорим о целых числах, наиболее вместительным типом данных является long , а если речь идет о числах с плавающей точкой — double . Но что если нужное нам число настолько велико, что не влезает даже в long ? Диапазон возможных значений Long довольно велик, но все-таки ограничен определенным размером — 64 бита. Что нам придумать, если наше Очень Большое Число весит 100 бит? К счастью, ничего придумывать не нужно. В Java для таких случаев были созданы два специальных класса — BigInteger (для целых чисел) и BigDecimal (для чисел с плавающей точкой). В чем же заключается их особенность? Прежде всего в том, у них теоретически нет максимального размера. Теоретически, потому что не бывает компьютеров с бесконечным размером памяти. И если ты создаешь в программе число размером больше, чем размер памяти компьютера, конечно, программа работать не будет. Но такого рода случаи маловероятны. Поэтому можно сказать, что размер чисел BigInteger и BigDecimal практически ничем не ограничен. Для чего используются эти классы? Прежде всего, для вычислений с крайне высокими требованиями к точности. Есть, к примеру, программы, в которых от точности вычислений может зависеть человеческая жизнь (ПО для самолетов и ракет или для медицинского оборудования). Поэтому, если даже 150-й разряд после запятой играет важную роль, BigDecimal — лучший выбор. Кроме того, довольно часто эти объекты применяются в мире финансов, где точность вычислений вплоть до самых мелких значений тоже крайне важна. Как работать с объектами BigInteger и BigDecimal и что важно о них помнить? Объекты этих классов создаются вот так: Передача строки в качестве параметра — только один из возможных конструкторов. Здесь мы используем строки, потому что наши числа превышают максимальные значения long и double , а как-то ведь надо объяснить компилятору, какое именно число мы хотим получить 🙂 Просто передать в конструктор число 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 не выйдет: Java попытается «вместить» переданное число в один из примитивных типов данных, но ни в один из них оно не влезет. Поэтому использование строки для передачи нужного числа — хороший вариант. Оба класса умеют автоматически извлекать из переданных строк числовые значения. Еще один важный момент, который необходимо помнить при работе с классами больших чисел — их объекты являются неизменяемыми ( Immutable ). С принципом неизменяемости ты уже хорошо знаком на примере класса String и классов-оберток для примитивов (Integer, Long и другими). Вывод в консоль: Наше число не изменилось, как и следовало ожидать. Чтобы операция сложения прошла успешно, необходимо создать новый объект и присвоить ему результат сложения. Вывод в консоль: Вот, теперь все работает как надо 🙂 Кстати, обратил внимание, как необычно выглядит операция сложения? Это еще один важный момент. Классы больших чисел не используют в своей работе операторы +-*/, а предоставляют вместо этого набор методов. Давай ознакомимся с основными из них (полный перечень методов ты, как и всегда, можешь найти в документации Oracle: здесь и здесь).

методы для осуществления арифметических операций: add() , subtract() , multiply() , divide() . Используются для операций сложения, вычитания, умножения и деления соответственно.

doubleValue() , intValue() , floatValue() , longValue() и т.д. — используются для преобразования большого числа к примитивному типу Java. Будь осторожен при их использовании и не забывай про разницу во вместимости!

Вывод в консоль:

min() и max() — позволяют найти минимальное и максимальное значение из двух переданных больших чисел.
Обрати внимание: методы не являются статическими!

Вывод в консоль:

Управление округлением BigDecimal

ROUND_CEILING — округление в большую сторону

ROUND_DOWN — отбрасывание разряда

ROUND_FLOOR — округление в меньшую сторону

ROUND_HALF_UP — округление в большую сторону, если число после запятой >= .5

ROUND_HALF_DOWN — округление в большую сторону, если число после запятой > .5

ROUND_HALF_EVEN — округление будет зависеть от цифры слева от запятой. Если цифра слева будет четной, то округление будет произведено вниз, в меньшую сторону. Если цифра слева от запятой нечетная, то округление будет произведено вверх.

Цифра слева от запятой — 2 — четная. Округление происходит вниз. Поскольку нам требуется 0 знаков после запятой, результатом будет 2.

Цифра слева от запятой — 3 — нечетная. Округление происходит вверх. Поскольку нам требуется 0 знаков после запятой, результатом будет 4.

ROUND_UNNECCESSARY — используется в тех случаях, когда в какой-то метод нужно передать режим округления, но число в округлении не нуждается. Если попробовать произвести округление числа при выставленном режиме ROUND_UNNECCESSARY — выброшено исключение ArithmeticException.

ROUND_UP — округление в большую сторону.

Источник

Оцените статью