As part of a school assignment, I am writing a program that will help determine the total price of a bag of bagels and the values that are being produced by my getTotalPrice() method is pushing out the correct values, but for some of them, there are a couple of zeros added to the end value. I tried using the setScale() method to round up the decimal to two spaces, but I was told that this was not a desirable solution. I've never used BigDecimals before, so I believe there's something I am missing within my code, any suggestions? Please let me know if more information is needed, I wasn't too sure what code to include/exclude.
// test that fails
@ParameterizedTest
@ArgumentsSource(BagArgumentsProvider_getTotalPrice.class)
void getTotalPrice_assertEquals_DiffBagelAmounts(Bag bag, BigDecimal cost) {
assertEquals(cost, bag.getTotalPrice());
}
//some to the produced errors
expected 3.99 but was 3.9900
expected 1.99 but was 1.9950
/**
* A bag of any quantity of a single type of bagel.
*
* @author Ellen Spertus
*
*/
public class Bagel {
private final Type type;
private Category currentCategory;
// Visible for Testing
protected static final BigDecimal OLD_FASHIONED_PRICE = new BigDecimal("0.50");
protected static final BigDecimal GOURMET_PRICE = new BigDecimal("0.70");
protected static final BigDecimal DAY_OLD_PRICE = new BigDecimal("0.35");
enum Type {
PLAIN("plain", Category.OLD_FASHIONED), POPPY_SEED("poppy seed",
Category.OLD_FASHIONED), SESAME_SEED("sesame seed", Category.OLD_FASHIONED), ONION(
"onion", Category.OLD_FASHIONED), EVERYTHING("everthing",
Category.OLD_FASHIONED), ASIAGO("asiago",
Category.GOURMET), BLUEBERRY("blueberry",
Category.GOURMET), CINNAMON_RAISIN(
"cinnamon raisin",
Category.GOURMET), SUN_DRIED_TOMATO(
"sun dried tomato",
Category.GOURMET);
private final String name;
private final Category category;
private Type(String name, Category category) {
this.name = name;
this.category = category;
}// end constructor
@Override
public String toString() {
return name;
}// end toString
}// end enum
enum Category {
OLD_FASHIONED(OLD_FASHIONED_PRICE), GOURMET(GOURMET_PRICE), DAY_OLD(DAY_OLD_PRICE);
private final BigDecimal price;
private Category(BigDecimal price) {
this.price = price;
}// end constructor
public BigDecimal getPrice() {
return price;
}// end getPrice
}// end enum
/**
* Constructs a bagel of the given type.
*
* @param the type
*/
public Bagel(Type type) {
this.type = type;
this.currentCategory = type.category;
}// end constructor
/**
* Gets the type of the bagel.
*
* @return the type of bagel
*/
public Type getType() {
return type;
}// end getType
/**
* Gets the category of the bagel.
*
* @return the category
*/
public Category getCategory() {
return currentCategory;
}// end get Category
/**
* Marks down this bagel.
*/
public void markDown() {
currentCategory = Category.DAY_OLD;
}// end markDown
}// end class
/**
* A type of bagel.
*
* @author Ellen Spertus
*/
public class Bag {
// We provide a Baker's Dozen: 13 bagels for the price of 12.
private static final int BUY_ONE_GET_ONE_FREE_QUANTITY = 13;
// If the Baker's Dozen discount doesn't apply, we give a percentage discount.
private static final int BULK_DISCOUNT_MINIMUM = 6;
private static final BigDecimal BULK_DISCOUNT_PERCENTAGE = new BigDecimal(".05");
private static final BigDecimal BULK_DISCOUNT_MULTIPLIER =
new BigDecimal("1.00").subtract(BULK_DISCOUNT_PERCENTAGE);
private final Bagel bagel;
private final int quantity;
/**
* Constructs a bag with the given quantity of the given type of bagel.
*
* @param bagel the type of bagel
* @param quantity the quantity
* @throws IllegalArguementException if the quantity passed is out of range from 1 to 13
*/
public Bag(Bagel bagel, int quantity) {
this.bagel = bagel;
this.quantity = quantity;
if (this.quantity > 13) {
throw new IllegalArgumentException("Orders must be under 13 bagels");
}
if (this.quantity < 1) {
throw new IllegalArgumentException("Not a valid order");
}
}// end constructor
/**
* Gets the bagel held in this bag.
*
* @return the bagel
*/
public Bagel getBagel() {
return bagel;
}// end getBagel
/**
* Gets the number of bagels in this bag.
*
* @return the number of bagels in this bag
*/
public int getQuantity() {
return quantity;
}// end getQuantity
/**
* Gets the total price for this bag of bagels. This may be less than the per-bagel price times
* the number of bagels because of quantity discounts.
*
* @return the total price
*/
public BigDecimal getTotalPrice() {
BigDecimal undiscountedPrice = getPerBagelPrice().multiply(new BigDecimal(quantity));
if (quantity == BUY_ONE_GET_ONE_FREE_QUANTITY) {
undiscountedPrice = undiscountedPrice.subtract(getPerBagelPrice());
}
if (quantity >= BULK_DISCOUNT_MINIMUM) {
undiscountedPrice = undiscountedPrice.multiply(BULK_DISCOUNT_MULTIPLIER);
}
//undiscountedPrice = undiscountedPrice.setScale(2, RoundingMode.HALF_UP);
return undiscountedPrice;
}// end getTotalPrice
/**
* Returns the price associated with the bagel type.
*
* @return price based on bagel type
*/
public BigDecimal getPerBagelPrice() {
return bagel.getCategory().getPrice();
}// end getPerBagelPrice
}
oh! I believe I've found the problem! the .multiply() method increases the decimal precision from 2 to 4. Solution then was to just create a new MathContext with precision of 3 and then use RoundingMode.Half_DOWN to push out the correct price value.
Let me know if there's better solution.
public BigDecimal getTotalPrice() {
System.out.println(getPerBagelPrice());
BigDecimal undiscountedPrice = getPerBagelPrice().multiply(new BigDecimal(quantity));
if (quantity == BUY_ONE_GET_ONE_FREE_QUANTITY) {
undiscountedPrice = undiscountedPrice.subtract(getPerBagelPrice());
}
if (quantity >= BULK_DISCOUNT_MINIMUM && quantity != 13) {
undiscountedPrice = undiscountedPrice.multiply(BULK_DISCOUNT_MULTIPLIER, new MathContext(3, RoundingMode.HALF_DOWN));
}
System.out.println(undiscountedPrice);
return undiscountedPrice;
}