Установите конкретную точность BigDecimal

У меня есть XSD, который требует, чтобы я использовал BigDecimal для широты/долготы. В настоящее время у меня есть широта/долгота как двойники, и я конвертирую их в BigDecimal, но мне требуется использовать только около 12 знаков точности. Я не смог понять, как это установить. Кто-нибудь может мне с этим помочь?


person Jason    schedule 28.02.2012    source источник
comment
Вы пытались дать конструктору MathContext?   -  person    schedule 28.02.2012
comment
Если я правильно понимаю, вы говорите о десятичных разрядах, а не о точности с плавающей запятой. По крайней мере, принятый ответ будет содержать 12 знаков после запятой, а не 12 цифр точности.   -  person vbence    schedule 05.03.2013


Ответы (4)


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

Несколько примеров в Clojure.

(.scale     0.00123M) ; 5
(.precision 0.00123M) ; 3

(В Clojure M обозначает литерал BigDecimal. Вы можете перевести Clojure на Java, если хотите, но я нахожу его более компактным, чем Java!)

Вы можете легко увеличить масштаб:

(.setScale 0.00123M 7) ; 0.0012300M

Но вы не можете уменьшить масштаб точно так же:

(.setScale 0.00123M 3) ; ArithmeticException Rounding necessary

Вам также нужно будет передать режим округления:

(.setScale 0.00123M 3 BigDecimal/ROUND_HALF_EVEN) ;
; Note: BigDecimal would prefer that you use the MathContext rounding
; constants, but I don't have them at my fingertips right now.

Таким образом, легко изменить масштаб. Но как насчет точности? Это не так просто, как вы могли бы надеяться!

Легко уменьшить точность:

(.round 3.14159M (java.math.MathContext. 3)) ; 3.14M

Но не очевидно, как увеличить точность:

(.round 3.14159M (java.math.MathContext. 7)) ; 3.14159M (unexpected)

Для скептиков дело не просто в том, что нули в конце не отображаются:

(.precision (.round 3.14159M (java.math.MathContext. 7))) ; 6 
; (same as above, still unexpected)

FWIW, Clojure осторожен с завершающими нулями и будет показывать их:

4.0000M ; 4.0000M
(.precision 4.0000M) ; 5

Снова в курс дела... Вы можете попробовать использовать BigDecimal, но он не устанавливает точность выше указанного вами количества цифр:

(BigDecimal. "3" (java.math.MathContext. 5)) ; 3M
(BigDecimal. "3.1" (java.math.MathContext. 5)) ; 3.1M

Таким образом, нет быстрого способа изменить точность. Я потратил время на борьбу с этим, когда писал этот вопрос и проект, над которым я работаю. Я считаю это в лучшем случае CRAZYTOWN API, а в худшем — ошибкой. Люди. Шутки в сторону?

Итак, насколько я могу судить, если вы хотите изменить точность, вам нужно будет выполнить следующие шаги:

  1. Поиск текущей точности.
  2. Поиск текущего масштаба.
  3. Рассчитайте изменение масштаба.
  4. Установите новый масштаб

Эти шаги, как код Clojure:

(def x 0.000691M) ; the input number
(def p' 1) ; desired precision
(def s' (+ (.scale x) p' (- (.precision x)))) ; desired new scale
(.setScale x s' BigDecimal/ROUND_HALF_EVEN)
; 0.0007M

Я знаю, это много шагов только для изменения точности!

Почему BigDecimal еще не предоставляет это? Я что-то проглядел?

person David J.    schedule 04.12.2013
comment
А как насчет BigDecimal.round(new MathContext(precision, RoundingModel.ROUND_HALF_EVEN))? - person Paŭlo Ebermann; 30.10.2014
comment
@PaŭloEbermann Вы спрашиваете или предлагаете? Ты пробовал это? Хотите поделиться подробными результатами для примеров, которые я запускаю в своем ответе? Если это так, я думаю, что это требует отдельного ответа. - person David J.; 31.10.2014
comment
Для тех, кто похож на меня и немного не знаком с Clojure, я думаю, что показанное здесь решение — это x.setScale(x.scale() + p - x.precision(), RoundingMode.HALF_UP), где x — это BigDecimal, которое вы хотите округлить, а p — это количество значащих цифр, которое вы хотите сохранить. Это работает для меня, но поправьте меня, если я ошибаюсь! - person Nateowami; 24.03.2015
comment
@Nateowami Мне нравится ваше решение, но, к сожалению, оно не работает правильно в угловом случае, например. за 99,99. Ваше утверждение (для 3 значащих цифр) превращает это в 100,0, хотя должно быть всего 100. Есть 1 дополнительная значащая цифра, потому что «переполнение» от 99 до 100 - person Kr1z; 17.08.2018
comment
@Kr1z Хороший вопрос. Предполагаю, что это ограничение этого ответа. Единственное, о чем я могу думать, это проверить потом и снова округлить, если количество цифр было неправильным. Я не думаю, что он может добавить цифру во второй раз. - person Nateowami; 18.08.2018

Вы можете использовать setScale() например

double d = ...
BigDecimal db = new BigDecimal(d).setScale(12, BigDecimal.ROUND_HALF_UP);
person Peter Lawrey    schedule 28.02.2012
comment
Дух. Оглядываясь назад на ошибку, которую я получил, он сказал, что округление необходимо. Я сделал только .setScale(12). Как только я подтвержу, что это работает, я сделаю это, как ответили. Спасибо. - person Jason; 28.02.2012
comment
Я не знаю, почему он просто не использует метод округления по умолчанию, как при кастинге, я видел только использование ROUND_HALF_UP. Это довольно педантично. ;) - person Peter Lawrey; 28.02.2012

 BigDecimal decPrec = (BigDecimal)yo.get("Avg");
 decPrec = decPrec.setScale(5, RoundingMode.CEILING);
 String value= String.valueOf(decPrec);

Таким образом, вы можете установить конкретную точность BigDecimal.

Значение decPrec равно 1.5726903423607562595809913132345426, которое округляется до 1.57267.

person E Hashmi    schedule 23.01.2014

Попробуйте этот код...

    Integer perc = 5;
    BigDecimal spread = BigDecimal.ZERO; 
    BigDecimal perc = spread.setScale(perc,BigDecimal.ROUND_HALF_UP);
    System.out.println(perc);

Результат: 0.00000

person Hakan Anlamaz    schedule 11.07.2016