Oc-windows.ru

IT Новости из мира ПК
11 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java double округление до 2 знаков

Андроид, финты ушами.

Мыши плакали, кололись, но продолжали грызть кактус.

четверг, 3 апреля 2014 г.

Округление чисел в Java

При выводе результатов числовых расчётов часто бывает ситуация, когда вместо десятичной дроби, например, 3,7, отображается число 3,6999999999999997. Это происходит из-за того, что далеко не все десятичные дроби можно точно представить в двоичном коде. В случае невозможности записать десятичную дробь точно, используется самое близкое к ней значение, которое можно выразить двоичным числом. При обратном переводе в десятичный формат это значение и даёт такие непонятные длинные дроби.

Перевод десятичных дробей в двоичную систему счисления

Невозможность записать некоторые десятичные дроби в двоичной системе (нулями и единицами) можно привести на примере обычного небольшого десятичного числа 20.1. Целая часть десятичных дробей переводится в двоичный вид точно так же, как целые числа, то есть складыванием положительных степеней двойки так, чтобы в итоге получить искомую сумму:

Получаем, что 2010 = 000101002

Как мы видим по таблице, справа налево вес каждого разряда двоичного числа увеличивается вдвое (положительные степени двойки). Набираем число 20, суммируя результаты этих степеней и отмечая единичками те степени, которые мы использовали. Вариант у нас только один – использовать степени 2 и 4, то есть числа 4 и 16, в сумме как раз дающие 20. Отметив их единичками, получаем двоичное число 00010100 или, если отбросить незначащие нули, 10100.

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

Эту таблицу мы можем продолжать вправо до бесконечности, но никогда не наберём полные 0.1. В итоге получаем, что 0,110 ≈ 00011001100112 = 0,099975585937510.

Таким образом, 20,110 ≈ 10100,00011001100112.

Хранение чисел с плавающей точкой в компьютере

В компьютере числа двойной точности (в Java тип Double) хранятся в экспоненциальном (т.н. научном, инженерном) формате. Этот формат, например, записывает десятичное число 20,1 как 2,01*10 1 , или сокращённо, как 2,01E+1. То есть, число после буквы E указывает, на сколько знаков следует сдвинуть запятую вправо (или влево, если число отрицательное) в числе, стоящем перед буквой

Переведённое в двоичную систему число 20,1, выглядящее как 10100,0001100110011 в экспоненциальной форме будет выглядеть как 1,01000001100110011 * 2 100 , или сокращённо 1,01000001100110011E+100 (двоичное 100 — это десятичное 4, то есть сдвигать запятую в числе 1,01000001100110011 следует на 4 знака вправо).

Для хранения этих чисел в компьютере отводится 64 бита (8 байт). Биты распределяются следующим образом. Для знака числа (плюс или минус) отводится 1 бит, для записи порядка (в нашем случае 100) — 11 бит, а для записи мантиссы (в нашем случае 1,01000001100110011) — 52 бита.

Схематично это выглядит так:

Чтобы иметь возможность записывать отрицательные порядки, за ноль в поле порядка принято число 1023 (в двоичном коде — 01111111111), что делит весь возможный диапазон порядков примерно пополам (от -1023 до 0 и от 0 до 1024). Поэтому наш порядок 100 фактически выглядит в битах, как 10000000011, ведь 1023 + 4 = 1027.

Кроме того, по причинам экономии на аппаратных ресурсах, самого левого бита мантиссы «в железе» не существует, но программно он всегда подразумевается. Этот бит принимается равным 1 во всех случаях, кроме случая нахождения в поле порядка специального числа -1023 (значения битов 00000000000). В нашем случае в поле порядка занесено число 4, а значит, левый бит признаётся единичным, и поэтому у нас в мантиссе фактически лежит лишь дробная часть, то есть число 01000001100110011.

Исходя из вышесказанного, для нашего числа 10100,0001100110011 значения всех 64 реальных битов и 1 подразумеваемого таковы:

Несколько слов про магическое число -1023 (значение битов 00000000000) в поле порядка. Как я уже говорил, это число просто устанавливает подразумеваемый бит целой части мантиссы в ноль. Если все остальные биты мантиссы также будут нулевыми, то хранимое таким образом в компьютере число, очевидно, будет являться нулём.

В случае же наличия в мантиссе ненулевых битов при том же числе -1023 в поле порядка, подразумеваемый бит целой части мантиссы также принимается равным нулю, но значение порядка подразумевается равным -1022. Грубо говоря, смена чисел в поле порядка с -1022 на -1023 и обратно влияют только на снятие или установку единичного бита целой части мантиссы, тогда как и то, и другое значение порядка подразумевается равным -1022.

Для чего же нужно было приравнивать целую часть мантиссы к нулю в ущерб возможности организовать ещё один порядок? Дело в том, что насколько бы ни был велик отрицательный порядок, то есть, насколько бы ни было мало выраженное им число, между ним и нулём у нас всегда будет оставаться незаполненная числами дырка.

Это легко понять, представив, как восстанавливается число из экспоненциального формата. Основание системы счисления (2) возводится в степень порядка и умножается на мантиссу. Самое маленькое число типа Double, которое мы можем так выразить, равно 1 * 2 -1022 . Ведь подразумеваемый бит равен единице, а значит, мантисса не может быть меньше единицы. Постепенно увеличивая мантиссу с 1 до почти 2 мы можем равномерно заполнить числами весь диапазон до предыдущего порядка 1 * 2 -1021 . Но у нас всегда остаётся незаполненный диапазон с другой стороны — в сторону нуля. И вот его то мы можем целиком заполнить только делая мантиссу меньше единицы. А для этого надо просто переключить подразумеваемый бит в ноль, что мы и делаем!

Существует ещё одно магическое число в поле порядка — 1024 (значение битов 11111111111). В зависимости от содержимого мантиссы, оно означает либо бесконечность (1 / 0 = ∞) либо неопределённость (неупорядоченное число, не число) (0 / 0 = NaN). Если мантисса нулевая — перед нами бесконечность, иначе — неопределённость.

Читать еще:  База данных javascript

Правильное округление в Java

Конечно, в Java есть функции, которые умеют округлять числа до нужного знака после запятой. Но иногда может встретится число, которое в десятичном виде округляется в большую сторону, а будучи записано в двоичном виде становится чуть меньше, и уже по правилам должно округляться в меньшую сторону. В качестве примера такого числа можно привести число 13,5465. Очевидно, что оно должно округляться в большую сторону до 13,547. Но при записи в двоичный код число становится равным 13,5464999…, и оно уже должно округляться в меньшую сторону до 13,546.

Но ведь мы мыслим и думаем в десятичной системе счисления, а значит, хотим видеть числа, всегда округлённые в правильную сторону вне зависимости от того, как они там внутри компьютера выглядят. Чтобы провести такое округление и отобразить число правильно, мы должны написать на Java небольшой код:

Теперь мы можем применить этот метод, например, если программируем под Андроид, при выводе результата result в поле EditText активности:

Рассмотрим, как же это работает. Метод roundUp принимает подлежащее округлению число value типа double и желаемое количество знаков после запятой digits типа int. Возвращает метод результат в виде числа типа BigDecimal. Тип BigDecimal хранит данные не в виде двоичного представления числа с плавающей запятой, о котором я рассказывал выше, а в виде числа с фиксированной запятой. Число с фиксированной запятой содержит в себе два отдельных целых числа. Это исходное число, но без запятой, и число, содержащее количество знаков после запятой. А целые числа всегда можно записать в двоичный формат абсолютно точно. Так что на выходе мы потенциально можем получить любую точную десятичную дробь.

Внутри нашего метода создаётся объект BigDecimal, который принимает на вход число с плавающей запятой value типа double, и методом setScale округляет его. Но при обычном использовании этого метода число округляется всё по тем же правилам, что приводит к неправильному округлению. В нашем же методе есть одна хитрость. Чтобы округление всегда происходило в нужную сторону, значение на вход метода BigDecimal мы передаём не в виде числа, а в виде строки. На этапе преобразования числа в строку, язык Java сам автоматически округляет число с достаточным количеством знаков, до степени, имеющей смысл (округляя весь хвост, начинающийся с последовательности девяток или нулей, подозревая в этом погрешность). Таким образом, мы подаём на вход BigDecimal строку с уже исправленной погрешностью.

Чтобы перевести число в строку, мы использовали трюк «»+value. При сложении числа со строкой (даже пустой) результат окажется тоже строкой.

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

Результат: 13,546 — неправильно!

Результат: 13,547 — правильно.

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

Если нам нужно вести расчёты вообще без погрешностей, возникающих из-за двоичного представления чисел, то вместо переменных типа Double следует сразу использовать тип BigDecimal, и все расчёты производить с использованием методов этого типа. Обычно такие расчёты используют в бухгалтерии, где при любых суммах для правильной отчётности важна точность до копейки. В других же случаях такой неестественной точности, как правило, не требуется, поскольку погрешность двоичного представления теряется на фоне погрешностей, уже существующих во входных данных из внешнего мира.

Округление в Java

Однако полученный результат может отличаться от ожидаемого. В частности, в данном случае y будет равно 5, несмотря на то, что по всем правилам округления должно быть 6. Чтобы избежать этой многозначности, можно воспользоваться «дедовским» способом, без привязки к возможностям языка.

Однако данная форма записи имеет множество недостатков, от неправильного результата в частных ситуациях до неудобочитаемой записи в целом. Классический метод округления до целого — round. Допустим, дано число n. Тогда для округления запишем:

Если n имеет дробную часть менее 0,5, то число округляется в меньшую сторону, в противном случае — в большую. То есть стандартный математический алгоритм.

Если же вам требуется java округление в меньшую сторону — вместо дополнительной математической операции лучше сразу воспользоваться готовой функций FLOOR:

Java округление в большую сторону выполняется методом CEIL:

Как и в жизни, округлять можно не только до целого числа, но и до определенного знака после запятой. Как и в первом случае, в java округление до сотых можно сделать чисто математически:

Однако запись не слишком удобна, если вам требуется в java округление до плавающего числа знаков с заданным поведением. С помощью методов перечисления RoundingMode() у вас есть такая возможность. Методы следующие:

  1. UP — округление в сторону большего числа для положительных чисел и меньшего для отрицательных.
  2. DOWN — округление в сторону меньшего числа для положительных чисел и большего для отрицательных.
  3. CEILING — округление в сторону большего и для положительных, и для отрицательных чисел.
  4. FLOOR — округление в сторону меньшего и для положительных, и для отрицательных чисел.
  5. HALF_UP — округление в большую сторону в случае числа вида 0.5;
  6. HALF_DOWN — округление в меньшую сторону в случае числа вида 0.5;
  7. HALF_EVEN — классическое округление

Выглядит подобное округление чисел так:

Цифра в скобках указывает в java округление до 2 знаков double типа.

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

Читать еще:  Перемножение матриц java

В Java есть целочисленные типы данных (long, int, char, short, byte) и есть типы с плавающей точкой (float, double), а по-русски — «с плавающей запятой» . Преобразование значений с дробной частью в целочисленные полезно для упрощения вычислений, последующего чтения и для сокращения объемов используемой памяти. Сделать это можно так:

Однако полученный результат может отличаться от ожидаемого. В частности, в данном случае y будет равно 5, несмотря на то, что по всем правилам округления должно быть 6. Чтобы избежать этой многозначности, можно воспользоваться «дедовским» способом, без привязки к возможностям языка.

Однако данная форма записи имеет множество недостатков, от неправильного результата в частных ситуациях до неудобочитаемой записи в целом. Классический метод округления до целого — round. Допустим, дано число n. Тогда для округления запишем:

Если n имеет дробную часть менее 0,5, то число округляется в меньшую сторону, в противном случае — в большую. То есть стандартный математический алгоритм.

Если же вам требуется java округление в меньшую сторону — вместо дополнительной математической операции лучше сразу воспользоваться готовой функций FLOOR:

Java округление в большую сторону выполняется методом CEIL:

Как и в жизни, округлять можно не только до целого числа, но и до определенного знака после запятой. Как и в первом случае, в java округление до сотых можно сделать чисто математически:

Однако запись не слишком удобна, если вам требуется в java округление до плавающего числа знаков с заданным поведением. С помощью методов перечисления RoundingMode() у вас есть такая возможность. Методы следующие:

  1. UP — округление в сторону большего числа для положительных чисел и меньшего для отрицательных.
  2. DOWN — округление в сторону меньшего числа для положительных чисел и большего для отрицательных.
  3. CEILING — округление в сторону большего и для положительных, и для отрицательных чисел.
  4. FLOOR — округление в сторону меньшего и для положительных, и для отрицательных чисел.
  5. HALF_UP — округление в большую сторону в случае числа вида 0.5;
  6. HALF_DOWN — округление в меньшую сторону в случае числа вида 0.5;
  7. HALF_EVEN — классическое округление

Выглядит подобное округление чисел так:

Цифра в скобках указывает в java округление до 2 знаков double типа.

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

Числа

В современном JavaScript существует два типа чисел:

  1. Обычные числа в JavaScript хранятся в 64-битном формате IEEE-754, который также называют «числа с плавающей точкой двойной точности» (double precision floating point numbers). Это числа, которые мы будем использовать чаще всего. Мы поговорим о них в этой главе.
  2. BigInt числа дают возможность работать с целыми числами произвольной длины. Они нужны достаточно редко и используются в случаях, когда необходимо работать со значениями более чем 2 53 или менее чем -2 53 . Так как BigInt числа нужны достаточно редко, мы рассмотрим их в отдельной главе BigInt.

В данной главе мы рассмотрим только первый тип чисел: числа типа number . Давайте глубже изучим, как с ними работать в JavaScript.

Способы записи числа

Представьте, что нам надо записать число 1 миллиард. Самый очевидный путь:

Но в реальной жизни мы обычно опускаем запись множества нулей, так как можно легко ошибиться. Укороченная запись может выглядеть как «1млрд» или «7.3млрд» для 7 миллиардов 300 миллионов. Такой принцип работает для всех больших чисел.

В JavaScript можно использовать букву «e» , чтобы укоротить запись числа. Она добавляется к числу и заменяет указанное количество нулей:

Другими словами, «e» производит операцию умножения числа на 1 с указанным количеством нулей.

Сейчас давайте запишем что-нибудь очень маленькое. К примеру, 1 микросекунду (одна миллионная секунды):

Записать микросекунду в укороченном виде нам поможет «e» .

Если мы подсчитаем количество нулей 0.000001 , их будет 6. Естественно, верная запись 1e-6 .

Другими словами, отрицательное число после «e» подразумевает деление на 1 с указанным количеством нулей:

Шестнадцатеричные, двоичные и восьмеричные числа

Шестнадцатеричные числа широко используются в JavaScript для представления цветов, кодировки символов и многого другого. Естественно, есть короткий стиль записи: 0x , после которого указывается число.

Не так часто используются двоичные и восьмеричные числа, но они также поддерживаются 0b для двоичных и 0o для восьмеричных:

Есть только 3 системы счисления с такой поддержкой. Для других систем счисления мы рекомендуем использовать функцию parseInt (рассмотрим позже в этой главе).

toString(base)

Метод num.toString(base) возвращает строковое представление числа num в системе счисления base .

base может варьироваться от 2 до 36 (по умолчанию 10 ).

base=16 — для шестнадцатеричного представления цвета, кодировки символов и т.д., цифры могут быть 0..9 или A..F .

base=2 — обычно используется для отладки побитовых операций, цифры 0 или 1 .

base=36 — максимальное основание, цифры могут быть 0..9 или A..Z . То есть, используется весь латинский алфавит для представления числа. Забавно, но можно использовать 36 -разрядную систему счисления для получения короткого представления большого числового идентификатора. К примеру, для создания короткой ссылки. Для этого просто преобразуем его в 36 -разрядную систему счисления:

Внимание! Две точки в 123456..toString(36) это не опечатка. Если нам надо вызвать метод непосредственно на числе, как toString в примере выше, то нам надо поставить две точки .. после числа.

Если мы поставим одну точку: 123456.toString(36) , тогда это будет ошибкой, поскольку синтаксис JavaScript предполагает, что после первой точки начинается десятичная часть. А если поставить две точки, то JavaScript понимает, что десятичная часть отсутствует, и начинается метод.

Также можно записать как (123456).toString(36) .

Округление

Одна из часто используемых операций при работе с числами – это округление.

В JavaScript есть несколько встроенных функций для работы с округлением:

Читать еще:  Java util timer

Math.floor Округление в меньшую сторону: 3.1 становится 3 , а -1.1 — -2 . Math.ceil Округление в большую сторону: 3.1 становится 4 , а -1.1 — -1 . Math.round Округление до ближайшего целого: 3.1 становится 3 , 3.6 — 4 , а -1.1 — -1 . Math.trunc (не поддерживается в Internet Explorer) Производит удаление дробной части без округления: 3.1 становится 3 , а -1.1 — -1 .

Ниже представлена таблица с различиями между функциями округления:

Math.floorMath.ceilMath.roundMath.trunc
3.13433
3.63443
-1.1-2-1-1-1
-1.6-2-1-2-1

Эти функции охватывают все возможные способы обработки десятичной части. Что если нам надо округлить число до n-ого количества цифр в дробной части?

Например, у нас есть 1.2345 и мы хотим округлить число до 2-х знаков после запятой, оставить только 1.23 .

Есть два пути решения:

Умножить и разделить.

Например, чтобы округлить число до второго знака после запятой, мы можем умножить число на 100 , вызвать функцию округления и разделить обратно.

Метод toFixed(n) округляет число до n знаков после запятой и возвращает строковое представление результата.

Округляет значение до ближайшего числа, как в большую, так и в меньшую сторону, аналогично методу Math.round :

Обратите внимание, что результатом toFixed является строка. Если десятичная часть короче, чем необходима, будут добавлены нули в конец строки:

Мы можем преобразовать полученное значение в число, используя унарный оператор + или Number() , пример с унарным оператором: +num.toFixed(5) .

Неточные вычисления

Внутри JavaScript число представлено в виде 64-битного формата IEEE-754. Для хранения числа используется 64 бита: 52 из них используется для хранения цифр, 11 из них для хранения положения десятичной точки (если число целое, то хранится 0), и один бит отведён на хранение знака.

Если число слишком большое, оно переполнит 64-битное хранилище, JavaScript вернёт бесконечность:

Наиболее часто встречающаяся ошибка при работе с числами в JavaScript – это потеря точности.

Посмотрите на это (неверное!) сравнение:

Да-да, сумма 0.1 и 0.2 не равна 0.3 .

Странно! Что тогда, если не 0.3 ?

Ой! Здесь гораздо больше последствий, чем просто некорректное сравнение. Представьте, вы делаете интернет-магазин и посетители формируют заказ из 2-х позиций за $0.10 и $0.20 . Итоговый заказ будет $0.30000000000000004 . Это будет сюрпризом для всех.

Но почему это происходит?

Число хранится в памяти в бинарной форме, как последовательность бит – единиц и нулей. Но дроби, такие как 0.1 , 0.2 , которые выглядят довольно просто в десятичной системе счисления, на самом деле являются бесконечной дробью в двоичной форме.

Другими словами, что такое 0.1 ? Это единица делённая на десять — 1/10 , одна десятая. В десятичной системе счисления такие числа легко представимы, по сравнению с одной третьей: 1/3 , которая становится бесконечной дробью 0.33333(3) .

Деление на 10 гарантированно хорошо работает в десятичной системе, но деление на 3 – нет. По той же причине и в двоичной системе счисления, деление на 2 обязательно сработает, а 1/10 становится бесконечной дробью.

В JavaScript нет возможности для хранения точных значений 0.1 или 0.2, используя двоичную систему, точно также, как нет возможности хранить одну третью в десятичной системе счисления.

Числовой формат IEEE-754 решает эту проблему путём округления до ближайшего возможного числа. Правила округления обычно не позволяют нам увидеть эту «крошечную потерю точности», но она существует.

Округление чисел в Java

  • написана командой Vertex Academy. Надеемся, что она Вам будет полезна. Приятного прочтения!
  • это одна из статей из нашего «Самоучителя по Java»

В пакете java.lang есть класс Math , а в этом классе есть 3 метода округления чисел с плавающей точкой до ближайшего целого числа:

Названия методов от английских слов:

round — от англ. «округлять»

floor — от англ. «пол»

ceiling — от англ. «потолок»

Давайте рассмотрим на примере как работают эти методы:

Пример №1

Если Вы запустите данный код на своем компьютере, в консоли Вы увидите:

Комментарии:

1. Math.round () данный метод округляет до ближайшего целого числа .

И действительно, на входе было 5.25, а метод нам вернул число 5, поскольку ближайшее целое число — это 5. Если бы, например, мы этим методом округляли число 8.75, то мы бы получили 9, потому что это ближайшее целое число.

Также обратите внимание, что данный метод возвращает значение типа int, то есть целое число. На входе было число 5.25, а метод вернул нам не 5.0, а 5

2. Math.floor ()данный метод округляет число до ближайшего целого вниз.

И действительно, у нас было число 5.25, ближайшее число вниз — это 5.0 А если бы мы, например, округляли с помощью этого метода число 8.75, то получили бы число 8.0, потому что ближайшее целое число вниз — это 8.0.

Теперь Вы понимаете почему метод называется floor — «пол»

Также обратите внимание, что данный метод возвращает значение типа double. На входе у нас было число 5.25, после округления мы получили 5.0 — тип double.

3. Math.ceil() — данный метод округляет число до ближайшего целого вверх. У нас было число 5.25, а вернул нам этот метод число 6.0 Даже если бы у нас было число 5.01, данный метод все равно вернул бы нам 6.0, потому что ближайшее целое число вверх — это 6.0

Вот почему данный метод называется ceil — от слова ceiling, «потолок». Также обратите внимание, что данный метод возвращает значение типа double.

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

Также Вы должны знать методы Math.random(), Math.max () и Math.min() . Об этих методах Вы можете почитать вот в этих 2 статьях:

Понравилась статья? А еще можно у нас пройти курс обучения по Java. Детальнее на сайте Vertex Academy.

Ссылка на основную публикацию
Adblock
detector