如何解决由四舍五入带来的不匹配?

huangapple go评论145阅读模式
英文:

How can I resolve mismatches brought about by rounding off?

问题

我正在开发一个Java API,其中包括两个端点。一个端点包含应支付的总金额,另一个端点包含端点一中总金额的分解或构成。其背后的逻辑是从数据库中获取货币A的值,并以货币B的汇率1B = 18.03A向用户显示它们。理想情况下,转换后的总金额和转换后的分解金额之和必须相同。在大多数情况下,代码正常运行。但有时候会出现不匹配的情况。以下是一个示例,
端点一中的总金额为19370.21B。端点二的分解为:124.5B,4245.71B,15000B。当我进行转换时会发生以下情况,

BigDecimal rate = BigDecimal.valueOf(18.03);

// 将总金额转换为货币B
BigDecimal bgtotal = BigDecimal.valueOf(19370.21);	  
BigDecimal totalToCurB = bgtotal.divide(rate, 2, RoundingMode.HALF_UP);
System.out.println(totalToCurB);//打印 1074.33

// 将各个项目的金额转换为货币B并求和
BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);
BigDecimal itemAToCurB = itemA.divide(rate, 2, RoundingMode.HALF_UP);
BigDecimal itemBToCurB = itemB.divide(rate, 2, RoundingMode.HALF_UP);
BigDecimal itemCToCurB = itemC.divide(rate, 2, RoundingMode.HALF_UP);

BigDecimal total = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
System.out.println(total);//打印 1074.34

如何避免这种不匹配?

英文:

I am working on a java API with two endpoints. One endpoint has a total amount to be paid, the other one has the breakdown or composition of the total amount in endpoint one. The logic behind is to fetch the values from the DB in currency A and show them to the user in currency B with the exchange rate at 1B = 18.03A. Ideally, the total converted total amount and the sum of the converted breakdown amounts must be the same. In most instances, the code is working fine. There are times though when I have mismatches. Here is an example,
The total amount is 19370.21B from endpoint one. The breakdown (from endpoint two): 124.5B,4245.71B,15000B. Here is what is happening when I do the conversion,

	BigDecimal rate = BigDecimal .valueOf(18.03);
	
	//Converting total amount to currency B
	
    BigDecimal bgtotal = BigDecimal .valueOf(19370.21);	  
    BigDecimal totalToCurB= bgtotal.divide(rate, 2, RoundingMode.HALF_UP);
    System.out.println(totalToCurB);//prints 1074.33
    
   //Converting individual item amounts to currency B and summing them
	BigDecimal itemA = BigDecimal .valueOf(124.5);
	BigDecimal itemB = BigDecimal .valueOf(4245.71);
	BigDecimal itemC = BigDecimal .valueOf(15000);
    BigDecimal itemAToCurB= itemA.divide(rate, 2, RoundingMode.HALF_UP);
	BigDecimal itemBToCurB= itemB.divide(rate, 2, RoundingMode.HALF_UP);
	BigDecimal itemCToCurB= itemC.divide(rate, 2, RoundingMode.HALF_UP);
	
	BigDecimal total = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
    System.out.println(total);//prints 1074.34

How can I avoid such kind of mismatches?

答案1

得分: 2

在操作中增加更多的小数位数。如果你在算术运算中只使用了两位小数,会出现不一致性。

itemAToCurB 将会是 6.91,itemBToCurB 是 235.48,itemCToCurB 是 831.95,这些的总和会是错误的。问题在于 itemAToCurB,因为除法结果更接近 6.90515806988352745424。通过将第二位小数四舍五入,你只是增加了不一致性。

当你在这些除法运算中使用更多小数位数时,四舍五入将在更不重要的位数上进行。你仍然可以只打印两位小数。

类似于:

BigDecimal rate = BigDecimal.valueOf(18.03);

BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);

BigDecimal itemAToCurB= itemA.divide(rate, 20, RoundingMode.HALF_UP);
BigDecimal itemBToCurB= itemB.divide(rate, 20, RoundingMode.HALF_UP);
BigDecimal itemCToCurB= itemC.divide(rate, 20, RoundingMode.HALF_UP);

BigDecimal total = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
System.out.println(total.setScale(2, RoundingMode.HALF_UP));//输出 1074.33
英文:

Set bigger scale on the operations. If you are working only with two decimal places on the arithmetic operations it will have discrepancies.

itemAToCurB would be 6.91, itemBToCurB 235.48 and itemCToCurB 831.95, sum of these will be wrong. The itemAToCurB is the issue here as the division is more like 6.90515806988352745424. By rounding half up on second decimal place you just added discrepancy.

When you make these divides with more decimal spaces the rounding will be done on much less significant scale. You can always print just two places.

Something like

BigDecimal rate = BigDecimal .valueOf(18.03);

BigDecimal itemA = BigDecimal .valueOf(124.5);
BigDecimal itemB = BigDecimal .valueOf(4245.71);
BigDecimal itemC = BigDecimal .valueOf(15000);

BigDecimal itemAToCurB= itemA.divide(rate, 20, RoundingMode.HALF_UP);
BigDecimal itemBToCurB= itemB.divide(rate, 20, RoundingMode.HALF_UP);
BigDecimal itemCToCurB= itemC.divide(rate, 20, RoundingMode.HALF_UP);

BigDecimal total = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
System.out.println(total.setScale(2, RoundingMode.HALF_UP));//prints 1074.33

答案2

得分: 0

Pavel Polivka所指出的,这个问题是由规模引起的,将其增加到3或4个小数位将解决这个问题。

或者,您可以保持2个小数位,但仅使用一个端点来获取分项和总金额。也就是说,不必调用端点1来获取总金额,而是可以调用端点2并对分项金额求和。

// 选项1:将小数位数增加到3或4
BigDecimal rate = BigDecimal.valueOf(18.03);

BigDecimal total = BigDecimal.valueOf(19370.21);
BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);

BigDecimal itemAToCurB = itemA.divide(rate, 3, RoundingMode.HALF_UP); //6.905
BigDecimal itemBToCurB = itemB.divide(rate, 3, RoundingMode.HALF_UP); //235.48
BigDecimal itemCToCurB = itemC.divide(rate, 3, RoundingMode.HALF_UP); //831.947

BigDecimal totalA = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
BigDecimal totalB = total.divide(rate, 2, RoundingMode.HALF_UP);

System.out.println(totalA.setScale(2, RoundingMode.HALF_UP)); //打印出1074.33
System.out.println(totalB.setScale(2, RoundingMode.HALF_UP)); //打印出1074.33
// 选项2:使用来自端点2的总金额,保留四舍五入误差
BigDecimal rate = BigDecimal.valueOf(18.03);

// BigDecimal total = BigDecimal.valueOf(19370.21);
BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);

BigDecimal itemAToCurB = itemA.divide(rate, 2, RoundingMode.HALF_UP); //6.91
BigDecimal itemBToCurB = itemB.divide(rate, 2, RoundingMode.HALF_UP); //235.48
BigDecimal itemCToCurB = itemC.divide(rate, 2, RoundingMode.HALF_UP); //831.95

BigDecimal totalA = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
BigDecimal totalB = totalA;

System.out.println(totalA.setScale(2, RoundingMode.HALF_UP)); //打印出1074.34
System.out.println(totalB.setScale(2, RoundingMode.HALF_UP)); //打印出1074.34
英文:

As indicated by Pavel Polivka, the problem has been introduced by scale, increasing it to 3 or 4 decimal places will solve the problem.

Alternatively, you can maintain 2 decimal places but only use one endpoint for both the break downs and total amounts. I.e. instead of calling endpoint 1 to get the total amount, you can instead call endpoint 2 and sum the breakdown amounts.

// Option 1: Increase decimal scale to 3 or 4
BigDecimal rate = BigDecimal .valueOf(18.03);

BigDecimal total = BigDecimal.valueOf(19370.21);
BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);

BigDecimal itemAToCurB= itemA.divide(rate, 3, RoundingMode.HALF_UP); //6.905
BigDecimal itemBToCurB= itemB.divide(rate, 3, RoundingMode.HALF_UP); //235.48
BigDecimal itemCToCurB= itemC.divide(rate, 3, RoundingMode.HALF_UP); //831.947

BigDecimal totalA = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
BigDecimal totalB = total.divide(rate, 2, RoundingMode.HALF_UP); 

System.out.println(totalA.setScale(2, RoundingMode.HALF_UP)); //prints 1074.33
System.out.println(totalB.setScale(2, RoundingMode.HALF_UP)); //prints 1074.33
// Option 2: Use total from Endpoint 2, Carrying the rounding-off errors
BigDecimal rate = BigDecimal .valueOf(18.03);

// BigDecimal total = BigDecimal.valueOf(19370.21);
BigDecimal itemA = BigDecimal.valueOf(124.5);
BigDecimal itemB = BigDecimal.valueOf(4245.71);
BigDecimal itemC = BigDecimal.valueOf(15000);

BigDecimal itemAToCurB= itemA.divide(rate, 2, RoundingMode.HALF_UP); //6.91
BigDecimal itemBToCurB= itemB.divide(rate, 2, RoundingMode.HALF_UP); //235.48
BigDecimal itemCToCurB= itemC.divide(rate, 2, RoundingMode.HALF_UP); //831.95

BigDecimal totalA = itemAToCurB.add(itemBToCurB).add(itemCToCurB);
BigDecimal totalB = totalA; 

System.out.println(totalA.setScale(2, RoundingMode.HALF_UP)); //prints 1074.34
System.out.println(totalB.setScale(2, RoundingMode.HALF_UP)); //prints 1074.34

huangapple
  • 本文由 发表于 2020年7月24日 18:16:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/63071569.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定