因BigDecimal類型數(shù)據(jù)引出的問題詳析
前言
我們都知道,java中對大小數(shù),高精度的計算都會用到BigDecimal.但是在實際應(yīng)用中,運用BigDecimal還是會遇到一些問題,下面話不多說了,來一起看看詳細(xì)的介紹吧
問題描述:
程序中需要判斷一個字段是否為0(字段類型為BigDecimal),想都沒想,對象的判斷用equals?結(jié)果卻與預(yù)期有一定的差距,看下面代碼及運行結(jié)果。
public static void main(String[] args) {
BigDecimal decimal1 = BigDecimal.valueOf(0);
BigDecimal decimal2 = new BigDecimal("0.00");
System.out.println("the result is " +decimal1.equals(decimal2));
}
運行結(jié)果:
the result is false
結(jié)論: BigDecimal類型比較相等不能簡單的通過equals方法實現(xiàn)。
BigDecimal類的equals方法源碼如下:
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)//這里會比較數(shù)字的精度
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflate().equals(xDec.inflate());
}
看上面的注釋可以知道,BigDecimal類的equals方法會判斷數(shù)字的精度,看下面的代碼及運行結(jié)果:
public static void main(String[] args) {
BigDecimal decimal1 = BigDecimal.valueOf(0).setScale(2);
BigDecimal decimal2 = new BigDecimal("0.00").setScale(2);
System.out.println("the result is " +decimal1.equals(decimal2));
}
運行結(jié)果:
the result is true
結(jié)論: 使用BigDecimal類equals方法判斷兩個BigDecimal類型的數(shù)據(jù)時,需要設(shè)置精度,否則結(jié)果可能不正確。
思考:每次都設(shè)置精度比較麻煩,有其他方式進(jìn)行相等的比較嗎?
看了下BigDecimal的方法列表,有一個名為compareTo的方法,通過注釋可知,貌似可以進(jìn)行不同精度的比較,看下面的代碼。
public static void main(String[] args) {
BigDecimal decimal1 = BigDecimal.valueOf(1.1);
BigDecimal decimal2 = new BigDecimal("1.10");
System.out.println("the result is " +decimal1.compareTo(decimal2));
}
運行結(jié)果:
the result is 0
0表示兩個數(shù)相等,所有可以通過compareTo實現(xiàn)不同精度的兩個BigDecimal類型的數(shù)字是否相等的比較
引出的問題:公司的項目中,為了避免由于精度丟失引起問題,凡是有精度要求的字段用的都是BigDecimal類型。數(shù)據(jù)持久層用的是Mybatis框架,Mybatis的mapper文件中有些條件判斷用的是BigDecimal對應(yīng)的字段,如下:
<select id="selectByCondition" resultType="com.scove.demo.domain.Score">
select * from tb_score where 1=1
<if test="score!=null and score!=0">
and score>#{score}
</if>
...
score是一個BigDecimal類型的字段,score!=0 Mybatis是如何進(jìn)行判斷的,會不會用的是上面的equals方法?如果是那么項目上線會不會捅大簍子,想到這兒,有點怕了。寫了個程序測了下,這樣寫完全沒問題,能夠達(dá)到我想要的目的。但是還是有點擔(dān)心,看看Mybatis底層是如何實現(xiàn)的吧,以免以后犯類似的錯誤嘛。
經(jīng)過分析調(diào)試,很快就找到了關(guān)鍵代碼的位置,如下:
public class ExpressionEvaluator {
public boolean evaluateBoolean(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return !new BigDecimal(String.valueOf(value)).equals(BigDecimal.ZERO);
}
return value != null;
}
...
public final class OgnlCache {
....
public static Object getValue(String expression, Object root) {
try {
Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());
return Ognl.getValue(parseExpression(expression), context, root);
} catch (OgnlException e) {
throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
}
}
用的是表達(dá)式求值,Ognl這個類的竟然沒有源碼,apache的官網(wǎng)上找了下,是有相應(yīng)的源碼的,只是需要單獨下載,真麻煩,算了不看了。據(jù)說底層用的是Spring 的ognl表達(dá)式,我也不 關(guān)心了。但是以后如果不確定mapper中的test是否正確咋個辦?
想了下,還是寫一個工具類在拿不準(zhǔn)的時候用一下吧,反正拿不準(zhǔn)的時候肯定很少,所以隨便寫了簡單的吧,如下:
public static void main(String[] args) {
ExpressionEvaluator evaluator = new ExpressionEvaluator();
String expression = "score!=null and score!=0";
DynamicContext context = new DynamicContext(new Configuration(), null);
context.bind("score", BigDecimal.valueOf(0.1));
Boolean flag = evaluator.evaluateBoolean(expression , context.getBindings());
System.out.println("the result is " +flag);
}
運行結(jié)果:
the result is true
總結(jié)
開發(fā)過程中,一定要細(xì)心去處理細(xì)節(jié)上的東西,不然一不小心就跳坑里了,輕則系統(tǒng)造成數(shù)據(jù)的不一致,修復(fù)數(shù)據(jù);重則造成重大的損失....
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
Java基礎(chǔ)之SpringBoot整合knife4j
Swagger現(xiàn)在已經(jīng)成了最流行的接口文檔生成與管理工具,但是你是否在用的時候也在吐槽,它是真的不好看,接口測試的json數(shù)據(jù)沒法格式化,測試地址如果更改了還要去改配置,接口測試時增加token驗證是真的麻煩…針對Swagger的種種缺點,Knife4j就呼之欲出了.需要的朋友可以參考下2021-05-05
關(guān)于@Component注解下的類無法@Autowired問題
這篇文章主要介紹了關(guān)于@Component注解下的類無法@Autowired問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
實例講解Java中random.nextInt()與Math.random()的基礎(chǔ)用法
今天小編就為大家分享一篇關(guān)于實例講解Java中random.nextInt()與Math.random()的基礎(chǔ)用法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02
Java使用ES?Client?調(diào)用滾動查詢及Elasticsearch滾動查詢Scrolling機制
Elasticsearch提供了一種稱為"滾動查詢"(Scrolling)的機制,用于處理大型數(shù)據(jù)集的分頁查詢,這篇文章給大家介紹滾動查詢的一般步驟及Java使用ESClient調(diào)用滾動查詢的方法,感興趣的朋友一起看看吧2023-08-08
Schedule定時任務(wù)在分布式產(chǎn)生的問題詳解
這篇文章主要介紹了Schedule定時任務(wù)在分布式產(chǎn)生的問題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
詳解Spring?@Lazy注解為什么能破解死循環(huán)
這篇文章主要來和大家探討一下Spring中的@Lazy注解為什么能破解死循環(huán),文中的示例代碼講解詳細(xì),具有一定的參考價值,需要的可以了解一下2023-07-07

