Rounding mismatch between ASP .net C# Decimal to Java Double - java

I am translating .NET code to Java and ran into precision not matching issue.
.NET code:
private decimal roundToPrecision(decimal number, decimal roundPrecision)
{
if (roundPrecision == 0)
return number;
decimal numberDecimalMultiplier = Math.Round(number / roundPrecision, MidpointRounding.AwayFromZero);
return numberDecimalMultiplier * roundPrecision;
}
Calling roundToPrecision(8.7250, 0.05); function in the code above gives me 8.75 which is expected.
The conversion/translation of the function to Java is as follows. I din't find exact
Math.Round option.
Java code:
public double roundToPrecision(double number, double roundPrecision) {
if (roundPrecision == 0)
return number;
int len = Double.toString(roundPrecision).split("\\.")[1].length();
double divisor = 0d;
switch (len) {
case 1:
divisor = 10d;
break;
case 2:
divisor = 100d;
break;
case 3:
divisor = 1000d;
break;
case 4:
divisor = 10000d;
break;
}
double numberDecimalMultiplier = Math.round(number / roundPrecision);
double res = numberDecimalMultiplier * roundPrecision;
return Math.round(res * divisor) / divisor;
}
Calling roundToPrecision(8.7250, 0.05); in the Java code gives me 8.7and this is not correct.
I even tried modifying code with BigDecimal as follows in Java using the reference here C# Double Rounding but have no luck.
public double roundToPrecision(double number, double roundPrecision) {
if (roundPrecision == 0)
return number;
int len = Double.toString(roundPrecision).split("\\.")[1].length();
double divisor = 0d;
switch (len) {
case 1:
divisor = 10d;
break;
case 2:
divisor = 100d;
break;
case 3:
divisor = 1000d;
break;
case 4:
divisor = 10000d;
break;
}
BigDecimal b = new BigDecimal(number / roundPrecision);
b = b.setScale(len,BigDecimal.ROUND_UP);
double numberDecimalMultiplier = Math.round(b.doubleValue());
double res = numberDecimalMultiplier * roundPrecision;
return Math.round(res * divisor) / divisor;
}
Please guide me for what I need to do to fix this.
Here are couple of scenarios to try out.
number = 10.05; precision = .1; expected = 10.1;
number = 10.12; precision = .01; expected = 10.12;
number = 8.7250; precision = 0.05; expected = 8.75;
number = 10.999; precision = 2; expected = 10;
number = 6.174999999999999; precision = 0.05; expected = 6.20;
Note: I have over 60 thousand numbers and precision can vary from 1 decimal to 4 decimal places. The output of .NET should match exactly to Java.

The problem comes from how doubles vs decimals are stored and represented in memory. See these links for more specifics: Doubles Decimals
Let's take a look at how they each work in your code. Using doubles, with arguments of 8.725 and 0.05. number / roundPrecision gives 174.499..., since doubles aren't able to exactly represent 174.5. With decimals number / roundPrecision gives 174.5, decimals are able to represent this exactly. So then when 174.499... gets rounded, it gets rounded down to 174 instead of 175.
Using BigDecimal is a step in the right direction. There is an issue with how it's being used in your code however. The problem comes when you're creating the BigDecimal value.
BigDecimal b = new BigDecimal(number / roundPrecision);
The BigDecimal is being created from a double, so the imprecision is already there. If you're able to create the BigDecimal arguments from a string that would be much better.
public static BigDecimal roundToPrecision(BigDecimal number, BigDecimal roundPrecision) {
if (roundPrecision.signum() == 0)
return number;
BigDecimal numberDecimalMultiplier = number.divide(roundPrecision, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
return numberDecimalMultiplier.multiply(roundPrecision);
}
BigDecimal n = new BigDecimal("-8.7250");
BigDecimal p = new BigDecimal("0.05");
BigDecimal r = roundToPrecision(n, p);
If the function must take in and return doubles:
public static double roundToPrecision(double number, double roundPrecision)
{
BigDecimal numberBig = new BigDecimal(number).
setScale(10, BigDecimal.ROUND_HALF_UP);
BigDecimal roundPrecisionBig = BigDecimal.valueOf(roundPrecision);
if (roundPrecisionBig.signum() == 0)
return number;
BigDecimal numberDecimalMultiplier = numberBig.divide(roundPrecisionBig, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
return numberDecimalMultiplier.multiply(roundPrecisionBig).doubleValue();
}
Keep in mind that doubles cannot exactly represent the same values which decimals can. So the function returning a double cannot have the exact output as the original C# function which returns decimals.

The real problem here is that the Math.round has two definitions. One returns a long, while the other returns an int! When you provide a double it runs the one for a long. To fix this simply cast your input to a float, to make it run the one to return the int.
double numberDecimalMultiplier = Math.round((float)(number / roundPrecision));

Related

Decimal separator in long (Java/Spring)

I need to put the decimal separator point in a Long, I have tried in several ways, but I need it to be dynamic since the decimal separator can change, I have tried with DecimalFormat format = new DecimalFormat("###.##"); but this is not dynamic and it doesn't work the way I wanted it to
Example 1
long amount = 123456;
int decimal = 2;
The result should be Double newAmount = 1234.56
Example 2
long amount = 123456;
int decimal = 4;
The result should be Double newAmount = 12.3456
If I understand correctly, this is what you are trying to achieve:
Long amount = 123456;
int decimal = 2;
double newAmount = amount.doubleValue();
newAmount = newAmount / Math.pow(10, decimal);
Use the pow method of java.lang.math to calculate the power of a number.
Be careful to declare your variable as an object of type Long and not a primitive type if you want to use one of its functions.
As suggested, it is even simpler to just use a double variable instead of a long from the start:
double amount = 123456;
int decimal = 2;
amount = amount / Math.pow(10, decimal);
You can get the required number by dividing the given number by 10 ^ decimalPlaces e.g.
public class Main {
public static void main(String[] args) {
// Test
System.out.println(getNum(123456, 2));
System.out.println(getNum(123456, 4));
}
static double getNum(long val, int decimalPlaces) {
return val / Math.pow(10, decimalPlaces);
}
}
Output:
1234.56
12.3456
All the other answers suggest converting to double and then scaling by powers of 10 before displaying. This will result in some unexpected results because of a loss of precision in the scaling operation. For the complete, gory details on why, please read
What Every Computer Scientist Should Know About Floating-Point Arithmetic and
Is Floating Point Broken?
As to your problem, you should be doing the work using BigDecimal. Converting from long (or Long) to BigDecimal does not lose precision, and will always produce the expected results.
BigDecimal even has a method to do the scaling for you:
long amount = 123456;
int decimal = 2;
BigDecimal n = BigDecimal.valueOf(amount).scaleByPowerOfTen(-decimal);
Output:
1234.56

Java Rounding to 15 decimal place

I have the below codes round the forward rate to 15 decimal place. When _ForwardRate is 13,555.0, the result return is wrong.
public double round(double Number, int Decimal_Place) {
if (Number==0) return 0;
double _plug = 0.000001;
if (Number < 0) {
_plug = -0.000001;
}
//Sometime a number is rounded down to 2.22499999999 by java.
//Actual precision is 2.245. Without this plug, a 2 dp rounding result
//in 2.22 when it should be 2.23
double _newNumber = Number;
if (Decimal_Place==2) {
_newNumber = _newNumber+_plug;
}
double _number_abs = Math.abs(_newNumber);
double _factor = Math.pow(10, Decimal_Place);
double _rd = Math.round(_number_abs * _factor);
double _r = _rd/_factor;
if (Number <= 0)
_r = _r * -1;
return _r;
}
Double _ForwardRate = getForward_rate();
BigDecimal _fwdrate_bd = BigDecimal.valueOf(_ForwardRate.doubleValue());
_ForwardRate = round(new Double(_fwdrate_bd.doubleValue()), 15);
Current result
9,223.372036854777
Expected result
13,555.000000000000000
Your problem is that Math.round(double a) returns long, and you're overflowing.
One easy way to do this, is to use BigDecimal:
public static double round(double number, int decimalPlaces) {
return BigDecimal.valueOf(number)
.setScale(decimalPlaces, RoundingMode.HALF_UP)
.doubleValue();
}
This allows you to control the rounding mode. Note that the rounding done by Math.round() is a HALF_CEILING which isn't supported by setScale().
You might want to consider doing all you math using BigDecimal, if you need that level of precision.
Consider:
double _number_abs = Math.abs(_newNumber);
At this point, _number_abs contains the value 13555.0
double _factor = Math.pow(10, Decimal_Place);
Now _factor contains 1.0E15
double _rd = Math.round(_number_abs * _factor);
According to the Javadoc
Math.round() Returns the closest long to the argument, with ties rounding to positive infinity.
Since _number_abs * _factor is 1.3555E19, which is larger than Long.MAX_VALUE, the result is Long.MAX_VALUE, i.e. the "closest" Long to the given value.

Java rounding error

I found that a rounding error with my Java application. The method used to round was:
public static double round(double value,double precision)
{
return Math.round(value * precision) / precision;
}
This could have an error (i.e. round(138.515,100) should return 138.52, and returns 138.51)
So I've created the following rounder:
// Mikeldi's rounder
public static double round2DecimalPlaces(double value,int decimalPlaces)
{
int s = value<0?-1:1;
double p = 1;
for (int i = 0; i < decimalPlaces; i++) {
p*=10;
}
double n = (long) value;
double d = ((value*10*p)-(n*10*p));
d +=s*5;
d /= 10;
d = (long)d;
d /= p;
return d+n;
}
I created this method since other rounding methods added too much latency to the system (low latency system). This one is around 10 times faster than the previous.
Note: This rounder will only use to round to possitive decimalPlaces (or 0).
Is there any problems I haven't see with this new rounder?
Thanks,
The Math#round method is not broken. 138.515 can't be exactly represented as a double. To see the exact value, you can use:
System.out.println(new BigDecimal(138.515d));
which prints:
138.5149999999999863575794734060764312744140625
It is therefore accurate for round to return 138.51. If you need more precision than double can give, you can use BigDecimal.
EDIT
If BigDecimal is not an option, and if the number of decimals is smallish (say 3 or 4 because these are prices for example), you can use longs instead with the last 4 digits being the decimals. So 138.51d would be 1385100L instead.

Android - Issue with conversion of double to Big Decimal

I am trying to convert the user's input from the EditText into a Big Decimal. However, I am having problems doing that. My result is not converted. For example, after the computation is done, the number will be 12.345678. After calling method round(), the rounded value is still the same. Am I doing something wrong? I'm dealing with money
.java
double totalPrice = Double.parseDouble(price.getText().toString());
int position = spinner.getSelectedItemPosition();
name = name1.getText().toString();
if(position == 0)
{
totalPrice = totalPrice * 1.07;
System.out.println(totalPrice);
}
else
{
totalPrice = (totalPrice * 1.1)*1.07;
System.out.println(totalPrice);
}
round(totalPrice, 2, BigDecimal.ROUND_HALF_UP);
}
}
public static double round(double unrounded, int precision, int roundingMode)
{
BigDecimal bd = new BigDecimal(unrounded);
BigDecimal rounded = bd.setScale(precision, roundingMode);
return rounded.doubleValue();
}
You're not using the result of round. You should at least be doing:
totalPrice = round(totalPrice, 2, BigDecimal.ROUND_HALF_UP);
However, you shouldn't be using double for currency values to start with. Use BigDecimal throughout - otherwise you may well find you get unexpected results due to the way binary floating point works.

round BigDecimal to nearest 5 cents

I'm trying to figure out how to round a monetary amount upwards to the nearest 5 cents. The following shows my expected results
1.03 => 1.05
1.051 => 1.10
1.05 => 1.05
1.900001 => 1.10
I need the result to be have a precision of 2 (as shown above).
Update
Following the advice below, the best I could do is this
BigDecimal amount = new BigDecimal(990.49)
// To round to the nearest .05, multiply by 20, round to the nearest integer, then divide by 20
def result = new BigDecimal(Math.ceil(amount.doubleValue() * 20) / 20)
result.setScale(2, RoundingMode.HALF_UP)
I'm not convinced this is 100% kosher - I'm concerned precision could be lost when converting to and from doubles. However, it's the best I've come up with so far and seems to work.
Using BigDecimal without any doubles (improved on the answer from marcolopes):
public static BigDecimal round(BigDecimal value, BigDecimal increment,
RoundingMode roundingMode) {
if (increment.signum() == 0) {
// 0 increment does not make much sense, but prevent division by 0
return value;
} else {
BigDecimal divided = value.divide(increment, 0, roundingMode);
BigDecimal result = divided.multiply(increment);
return result;
}
}
The rounding mode is e.g. RoundingMode.HALF_UP. For your examples, you actually want RoundingMode.UP (bd is a helper which just returns new BigDecimal(input)):
assertEquals(bd("1.05"), round(bd("1.03"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.10"), round(bd("1.051"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.05"), round(bd("1.05"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.95"), round(bd("1.900001"), bd("0.05"), RoundingMode.UP));
Also note that there is a mistake in your last example (rounding 1.900001 to 1.10).
I'd try multiplying by 20, rounding to the nearest integer, then dividing by 20. It's a hack, but should get you the right answer.
I wrote this in Java a few years ago: https://github.com/marcolopes/dma/blob/master/org.dma.java/src/org/dma/java/math/BusinessRules.java
/**
* Rounds the number to the nearest<br>
* Numbers can be with or without decimals<br>
*/
public static BigDecimal round(BigDecimal value, BigDecimal rounding, RoundingMode roundingMode){
return rounding.signum()==0 ? value :
(value.divide(rounding,0,roundingMode)).multiply(rounding);
}
/**
* Rounds the number to the nearest<br>
* Numbers can be with or without decimals<br>
* Example: 5, 10 = 10
*<p>
* HALF_UP<br>
* Rounding mode to round towards "nearest neighbor" unless
* both neighbors are equidistant, in which case round up.
* Behaves as for RoundingMode.UP if the discarded fraction is >= 0.5;
* otherwise, behaves as for RoundingMode.DOWN.
* Note that this is the rounding mode commonly taught at school.
*/
public static BigDecimal roundUp(BigDecimal value, BigDecimal rounding){
return round(value, rounding, RoundingMode.HALF_UP);
}
/**
* Rounds the number to the nearest<br>
* Numbers can be with or without decimals<br>
* Example: 5, 10 = 0
*<p>
* HALF_DOWN<br>
* Rounding mode to round towards "nearest neighbor" unless
* both neighbors are equidistant, in which case round down.
* Behaves as for RoundingMode.UP if the discarded fraction is > 0.5;
* otherwise, behaves as for RoundingMode.DOWN.
*/
public static BigDecimal roundDown(BigDecimal value, BigDecimal rounding){
return round(value, rounding, RoundingMode.HALF_DOWN);
}
Here are a couple of very simple methods in c# I wrote to always round up or down to any value passed.
public static Double RoundUpToNearest(Double passednumber, Double roundto)
{
// 105.5 up to nearest 1 = 106
// 105.5 up to nearest 10 = 110
// 105.5 up to nearest 7 = 112
// 105.5 up to nearest 100 = 200
// 105.5 up to nearest 0.2 = 105.6
// 105.5 up to nearest 0.3 = 105.6
//if no rounto then just pass original number back
if (roundto == 0)
{
return passednumber;
}
else
{
return Math.Ceiling(passednumber / roundto) * roundto;
}
}
public static Double RoundDownToNearest(Double passednumber, Double roundto)
{
// 105.5 down to nearest 1 = 105
// 105.5 down to nearest 10 = 100
// 105.5 down to nearest 7 = 105
// 105.5 down to nearest 100 = 100
// 105.5 down to nearest 0.2 = 105.4
// 105.5 down to nearest 0.3 = 105.3
//if no rounto then just pass original number back
if (roundto == 0)
{
return passednumber;
}
else
{
return Math.Floor(passednumber / roundto) * roundto;
}
}
In Scala I did the following (Java below)
import scala.math.BigDecimal.RoundingMode
def toFive(
v: BigDecimal,
digits: Int,
roundType: RoundingMode.Value= RoundingMode.HALF_UP
):BigDecimal = BigDecimal((2*v).setScale(digits-1, roundType).toString)/2
And in Java
import java.math.BigDecimal;
import java.math.RoundingMode;
public static BigDecimal toFive(BigDecimal v){
return new BigDecimal("2").multiply(v).setScale(1, RoundingMode.HALF_UP).divide(new BigDecimal("2"));
}
Based on your edit, another possible solution would be:
BigDecimal twenty = new BigDecimal(20);
BigDecimal amount = new BigDecimal(990.49)
// To round to the nearest .05, multiply by 20, round to the nearest integer, then divide by 20
BigDecimal result = new BigDecimal(amount.multiply(twenty)
.add(new BigDecimal("0.5"))
.toBigInteger()).divide(twenty);
This has the advantage, of being guaranteed not to lose precision, although it could potentially be slower of course...
And the scala test log:
scala> var twenty = new java.math.BigDecimal(20)
twenty: java.math.BigDecimal = 20
scala> var amount = new java.math.BigDecimal("990.49");
amount: java.math.BigDecimal = 990.49
scala> new BigDecimal(amount.multiply(twenty).add(new BigDecimal("0.5")).toBigInteger()).divide(twenty)
res31: java.math.BigDecimal = 990.5
For this test to pass :
assertEquals(bd("1.00"), round(bd("1.00")));
assertEquals(bd("1.00"), round(bd("1.01")));
assertEquals(bd("1.00"), round(bd("1.02")));
assertEquals(bd("1.00"), round(bd("1.024")));
assertEquals(bd("1.05"), round(bd("1.025")));
assertEquals(bd("1.05"), round(bd("1.026")));
assertEquals(bd("1.05"), round(bd("1.049")));
assertEquals(bd("-1.00"), round(bd("-1.00")));
assertEquals(bd("-1.00"), round(bd("-1.01")));
assertEquals(bd("-1.00"), round(bd("-1.02")));
assertEquals(bd("-1.00"), round(bd("-1.024")));
assertEquals(bd("-1.00"), round(bd("-1.0245")));
assertEquals(bd("-1.05"), round(bd("-1.025")));
assertEquals(bd("-1.05"), round(bd("-1.026")));
assertEquals(bd("-1.05"), round(bd("-1.049")));
Change ROUND_UP in ROUND_HALF_UP :
private static final BigDecimal INCREMENT_INVERTED = new BigDecimal("20");
public BigDecimal round(BigDecimal toRound) {
BigDecimal divided = toRound.multiply(INCREMENT_INVERTED)
.setScale(0, BigDecimal.ROUND_HALF_UP);
BigDecimal result = divided.divide(INCREMENT_INVERTED)
.setScale(2, BigDecimal.ROUND_HALF_UP);
return result;
}
public static BigDecimal roundTo5Cents(BigDecimal amount)
{
amount = amount.multiply(new BigDecimal("2"));
amount = amount.setScale(1, RoundingMode.HALF_UP);
// preferred scale after rounding to 5 cents: 2 decimal places
amount = amount.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
return amount;
}
Note that this is basically the same answer as John's.
public static void roundUp()
{
try
{
System.out.println("Enter the currency : $");
Scanner keyboard = new Scanner(System.in);
String myint = keyboard.next();
if (!isEmptyOrBlank(myint).booleanValue())
{
BigDecimal d = new BigDecimal(myint);
System.out.println("Enter the round up factor: $");
String roundUpFactor = keyboard.next();
if (!isEmptyOrBlank(roundUpFactor).booleanValue())
{
BigDecimal scale = new BigDecimal(roundUpFactor);
BigDecimal y = d.divide(scale, MathContext.DECIMAL128);
BigDecimal q = y.setScale(0, 0);
BigDecimal z = q.multiply(scale);
System.out.println("Final price after rounding up to " + roundUpFactor + " is : $" + z);
System.out.println("Want to try with other price Y/N :");
String exit = keyboard.next();
if ((!isEmptyOrBlank(exit).booleanValue()) && ("y".equalsIgnoreCase(exit))) {
roundUp();
} else {
System.out.println("See you take care");
}
}
}
else
{
System.out.println("Please be serious u r dealing with critical Tx Pricing");
}
}
catch (Exception e)
{
System.out.println("Please be serious u r dealing with critical Tx Pricing enter correct rounding off value");
}
}
Tom has the right idea, but you need to use BigDecimal methods, since you ostensibly are using BigDecimal because your values are not amenable to a primitive datatype. Something like:
BigDecimal num = new BigDecimal(0.23);
BigDecimal twenty = new BigDecimal(20);
//Might want to use RoundingMode.UP instead,
//depending on desired behavior for negative values of num.
BigDecimal numTimesTwenty = num.multiply(twenty, new MathContext(0, RoundingMode.CEILING));
BigDecimal numRoundedUpToNearestFiveCents
= numTimesTwenty.divide(twenty, new MathContext(2, RoundingMode.UNNECESSARY));
You can use plain double to do this.
double amount = 990.49;
double rounded = ((double) (long) (amount * 20 + 0.5)) / 20;
EDIT: for negative numbers you need to subtract 0.5

Categories