debugjavaMinor
BigDecimal wrapper that ignores zero operands
Viewed 0 times
bigdecimalignoreswrapperthatoperandszero
Problem
I want to perform mathematical operations on a range of values, where zeros should be ignored. This means that multiplication by zero does not result in zero but returns the first operand and likewise (\$5x0=5\$), division by zero does not throw an
I would like to avoid testing for zero every time I need to make a calculation and would prefer that the calculation logic takes care of it.
My question is about the best way of approaching this problem. I will be using
With this in mind, I developed the following code snippet:
I expect to use it like this:
One of the problems that this is intended to resolve is in the calculation of quantity where it is not known i
ArithmeticException but returns the first operand (\$5/0=5\$). I would like to avoid testing for zero every time I need to make a calculation and would prefer that the calculation logic takes care of it.
My question is about the best way of approaching this problem. I will be using
BigDecimal and thought of a simple implementation of the decorator pattern that adds functionality to the multiply and divide methods.With this in mind, I developed the following code snippet:
public class BigDecimalWrapper extends BigDecimal {
public BigDecimalWrapper multiply(BigDecimal augend){
if(augend.compareTo(BigDecimal.ZERO) != 0){
return new BigDecimalWrapper(super.multiply(augend).doubleValue());
} else {
return this;
}
}
public BigDecimalWrapper divide(BigDecimal augend){
if(augend.compareTo(BigDecimal.ZERO) != 0){
return new BigDecimalWrapper(super.divide(augend).doubleValue());
} else {
return this;
}
}
public BigDecimalWrapper add(BigDecimal augend){
return new BigDecimalWrapper(super.add(augend).doubleValue());
}
public BigDecimalWrapper subtract(BigDecimal augend){
return new BigDecimalWrapper(super.subtract(augend).doubleValue());
}
// Some code omitted for brevity.
}I expect to use it like this:
@Test
public void shouldMultiply() {
BigDecimalWrapper v = new BigDecimalWrapper(10).multiply(BigDecimal.ZERO);
assertThat(v).isEqualTo(new BigDecimal(10));
}
@Test
public void shouldDivide() {
BigDecimalWrapper v = new BigDecimalWrapper(10).divide(BigDecimal.ZERO);
assertThat(v).isEqualTo(new BigDecimal(10));
}One of the problems that this is intended to resolve is in the calculation of quantity where it is not known i
Solution
There are big problems with your approach.
Cloning
Cloning a
For example, try this code:
On my computer the above code prints:
The equality test fails for as simple values as
As such, your current implementation of cloning doubles is effectively broken, for example this test case fails:
You can fix it by replacing
the unit test will pass.
Cloning
Creating
Your current approach forces you to clone.
Since cloning is non-sense, this indicates a code smell,
and that a better solution is probably available.
Composition over inheritance
A better approach would be to avoid the strange cloning, by making the decorated object a final field in your wrapper class. Add a constructor that takes a
By the way, putting the decorated object into a field of the decorator class is the common technique when implementing this pattern (composition instead of inheritance). It makes it possible to nest multiple decorators in arbitrary order. Inheritance is a very tight coupling, composition gives you much more flexibility.
For example something like this:
Since the class no longer extends
I added a
The unit tests can be modified to work with this, and they pass:
You might not like that fact the
But maybe that's a good thing.
With your implementation,
if somebody ever tries to use a
he might be surprised that multiplication with zero doesn't work as expected from a
For this reason I think it's a good thing for
If, however, you really want
then the most sensible solution is to keep your original implementation,
with
Cloning
BigDecimal with .doubleValue() is brokenCloning a
BigDecimal bd using new BigDecimal(bd.doubleValue()) doesn't work in most practical cases.For example, try this code:
BigDecimal bd = BigDecimal.valueOf(0.02d);
BigDecimal clone = new BigDecimal(bd.doubleValue());
System.out.println(bd);
System.out.println(clone);
System.out.println(bd.equals(clone));On my computer the above code prints:
0.02
0.0200000000000000004163336342344337026588618755340576171875
falseThe equality test fails for as simple values as
2d. It passes for a simple 2, but that's not very reassuring.As such, your current implementation of cloning doubles is effectively broken, for example this test case fails:
@Test
public void multiply_2d_with_3d() {
BigDecimalWrapper v = new BigDecimalWrapper(2d).multiply(BigDecimal.valueOf(3d);
assertThat(v).isEqualTo(new BigDecimal(2d).multiply(BigDecimal.valueOf(3d));
}You can fix it by replacing
.doubleValue() with .toString(),the unit test will pass.
Cloning
BigDecimal is a strange thing to doBigDecimal instances are immutable, so you can safely pass around copies.Creating
new instances is completely unnecessary.Your current approach forces you to clone.
Since cloning is non-sense, this indicates a code smell,
and that a better solution is probably available.
Composition over inheritance
A better approach would be to avoid the strange cloning, by making the decorated object a final field in your wrapper class. Add a constructor that takes a
BigDecimal, and call that one instead of super. By the way, putting the decorated object into a field of the decorator class is the common technique when implementing this pattern (composition instead of inheritance). It makes it possible to nest multiple decorators in arbitrary order. Inheritance is a very tight coupling, composition gives you much more flexibility.
For example something like this:
class BigDecimalWrapper {
private final BigDecimal bigDecimal;
public BigDecimalWrapper(double num) {
bigDecimal = new BigDecimal(num);
}
public BigDecimalWrapper(BigDecimal bigDecimal) {
this.bigDecimal = bigDecimal;
}
public BigDecimalWrapper multiply(BigDecimal augend) {
if (augend.compareTo(BigDecimal.ZERO) != 0) {
return new BigDecimalWrapper(bigDecimal.multiply(augend));
} else {
return this;
}
}
public BigDecimal bigDecimalValue() {
return bigDecimal;
}
}Since the class no longer extends
BigDecimal,I added a
bigDecimalValue() method to get access to the wrapped object.The unit tests can be modified to work with this, and they pass:
@Test
public void shouldMultiply() {
BigDecimalWrapper v = new BigDecimalWrapper(10).multiply(BigDecimal.ZERO);
assertThat(v.bigDecimalValue()).isEqualTo(new BigDecimal(10));
}
@Test
public void multiply_2d_with_3d() {
BigDecimalWrapper v = new BigDecimalWrapper(2d).multiply(BigDecimal.valueOf(3d);
assertThat(v.bigDecimalValue()).isEqualTo(new BigDecimal(2d).multiply(BigDecimal.valueOf(3d));
}You might not like that fact the
BigDecimalWrapper is no longer a BigDecimal.But maybe that's a good thing.
With your implementation,
if somebody ever tries to use a
BigDecimalWrapper instance as a BigDecimal,he might be surprised that multiplication with zero doesn't work as expected from a
BigDecimal.For this reason I think it's a good thing for
BigDecimalWrapper to not pretend to be a BigDecimal.If, however, you really want
BigDecimalWrapper to be a BigDecimal,then the most sensible solution is to keep your original implementation,
with
doubleValue() replaced with toString().Code Snippets
BigDecimal bd = BigDecimal.valueOf(0.02d);
BigDecimal clone = new BigDecimal(bd.doubleValue());
System.out.println(bd);
System.out.println(clone);
System.out.println(bd.equals(clone));0.02
0.0200000000000000004163336342344337026588618755340576171875
false@Test
public void multiply_2d_with_3d() {
BigDecimalWrapper v = new BigDecimalWrapper(2d).multiply(BigDecimal.valueOf(3d);
assertThat(v).isEqualTo(new BigDecimal(2d).multiply(BigDecimal.valueOf(3d));
}class BigDecimalWrapper {
private final BigDecimal bigDecimal;
public BigDecimalWrapper(double num) {
bigDecimal = new BigDecimal(num);
}
public BigDecimalWrapper(BigDecimal bigDecimal) {
this.bigDecimal = bigDecimal;
}
public BigDecimalWrapper multiply(BigDecimal augend) {
if (augend.compareTo(BigDecimal.ZERO) != 0) {
return new BigDecimalWrapper(bigDecimal.multiply(augend));
} else {
return this;
}
}
public BigDecimal bigDecimalValue() {
return bigDecimal;
}
}@Test
public void shouldMultiply() {
BigDecimalWrapper v = new BigDecimalWrapper(10).multiply(BigDecimal.ZERO);
assertThat(v.bigDecimalValue()).isEqualTo(new BigDecimal(10));
}
@Test
public void multiply_2d_with_3d() {
BigDecimalWrapper v = new BigDecimalWrapper(2d).multiply(BigDecimal.valueOf(3d);
assertThat(v.bigDecimalValue()).isEqualTo(new BigDecimal(2d).multiply(BigDecimal.valueOf(3d));
}Context
StackExchange Code Review Q#98040, answer score: 5
Revisions (0)
No revisions yet.