设置 BigDecimal 的特定精度

59

我有一个XSD,要求我在经纬度上使用BigDecimal。目前我的经纬度是double类型,我将其转换为BigDecimal,但我只需要使用约12位精度。我一直没有找到如何设置精度的方法。有人能帮助我吗?


你尝试过给构造函数传递一个MathContext吗? - user684934
1
如果我看得正确,您说的是小数位数,而不是浮点精度。至少接受的答案将导致12个小数位,而不是12位精度。 - vbence
6个回答

102

你可以使用setScale()方法。

double d = ...
BigDecimal db = new BigDecimal(d).setScale(12, BigDecimal.ROUND_HALF_UP);

1
看来我犯了一个错误。回头看看我得到的错误,它说需要四舍五入。我只做了.setScale(12)。一旦确认它可以工作,我会将其标记为已回答。谢谢。 - Jason
我不知道为什么它不像强制转换那样使用默认的四舍五入方法,我只见过使用ROUND_HALF_UP。这有点学究气。;) - Peter Lawrey
我有点困惑。问题谈论精度,但你的答案谈论比例。是问题使用了错误的术语还是答案?据我所知,BigDecimal的精度和比例是互相排斥的概念。 - Gili

47
问题的标题是关于精度的。BigDecimal区分了比例和精度。比例是小数点后的位数。您可以将精度视为significant figures的数量,也称为有效数字。
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)

顺便提一下,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

很遗憾,没有快速更改精度的方法。在撰写此问题和我正在开展的项目时,我花了很多时间与之对抗。我认为这是一个糟糕的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没有提供这个功能呢?我有什么遗漏的吗?

2
BigDecimal.round(new MathContext(precision, RoundingModel.ROUND_HALF_EVEN)) 怎么样? - Paŭlo Ebermann
1
@PaŭloEbermann你是在询问还是建议?你试过了吗?能否分享我在答案中运行的示例的详细结果?如果可以,我认为这值得一个单独的回答。 - David J.
5
对于像我一样不懂Clojure的人,我认为这里展示的解决方案是 x.setScale(x.scale() + p - x.precision(), RoundingMode.HALF_UP),其中x是您想要舍入的BigDecimal,而p是您想要保留的有效数字位数。对我来说它起作用了,但如果我错了,请纠正我! - Nateowami
@Nateowami,我喜欢你的解决方案,但不幸的是,在某些特殊情况下似乎无法正常工作,例如对于99.99。你的语句(针对3个有效数字)将其转换为100.0,而实际上应该只是100。这是因为从99到100的“溢出”导致了1个额外的有效数字。 - Kr1z
@Kr1z 很好的观点。猜测这是答案的限制。我能想到的唯一办法就是事后检查,如果数字位数不对再四舍五入一次。我认为第二次添加一个数字是不可能的。 - Nateowami

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

通过这种方式,您可以设置 BigDecimal 的特定精度。

decPrec 的值为 1.5726903423607562595809913132345426,四舍五入后为 1.57267


1

在处理BigDecimal时,精度和比例是两个非常重要的术语,您可以尝试单独设置它们或同时设置它们:

1. Precision

    BigDecimal.ZERO.round(new MathContext(30, RoundingMode.FLOOR)); or 

    BigDecimal.valueOf(5878).round(new MathContext(10, RoundingMode.FLOOR));

2. Scale

    BigDecimal.valueOf(5878).setScale(7, RoundingMode.CEILING);

3. Both precision and scale

    BigDecimal.ONE.round(new MathContext(30, RoundingMode.FLOOR)).setScale(7, RoundingMode.FLOOR);

例如:输入:321231.324 精度:9 刻度:3

0

BigDecimal 的精度和刻度是相互排斥的概念。引用 https://dev59.com/-VsW5IYBdhLWcg3wDzvs#35435775

精度:有效数字总位数

刻度:小数点右侧的数字位数

要更改 BigDecimal 的精度,请使用 BigDecimal.round(new MathContext(precision, roundingMode))

要更改 BigDecimal 的刻度,请使用 BigDecimal.setScale(scale, roundingMode)


0

尝试这段代码...

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

结果:0.00000


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接