Your precision loss requires coordinated fixes across decimal configuration, aggregation queries, rounding strategy, and calculation order.
Decimal Precision Configuration:
First, update database schema to support 6 decimal places:
ALTER TABLE CostData
MODIFY COLUMN unitCost DECIMAL(14,6),
MODIFY COLUMN extendedCost DECIMAL(16,6);
Use DECIMAL(14,6) for unit costs (supports values up to $99,999,999.999999) and DECIMAL(16,6) for extended costs to handle large quantities.
In formula-mgmt configuration, set precision attributes:
<costFormula id="BOM_ROLLUP" precision="6"
roundingMode="HALF_UP" roundAt="FINAL" />
The roundAt=“FINAL” is critical - maintain full precision through intermediate calculations, round only when presenting final product cost.
Aggregation Query Optimization:
Rewrite aggregation using BigDecimal:
BigDecimal totalCost = BigDecimal.ZERO;
for (Component comp : bomComponents) {
BigDecimal unitCost = new BigDecimal(comp.getUnitCost())
.setScale(6, RoundingMode.HALF_UP);
BigDecimal quantity = new BigDecimal(comp.getQuantity());
totalCost = totalCost.add(unitCost.multiply(quantity));
}
return totalCost.setScale(2, RoundingMode.HALF_UP); // Round at final step only
Never use double arithmetic for currency. The statement double totalCost = 0.0 guarantees precision loss. BigDecimal maintains exact decimal representation.
Rounding Strategy:
Implement a three-tier rounding strategy:
- Component level: Store at 6 decimals, no rounding
- Assembly level: Calculate at 6 decimals, display at 4
- Product level: Calculate at 6 decimals, round to 2 for pricing
Use HALF_UP rounding (banker’s rounding) consistently. Configure in site.xconf:
formula.cost.internalPrecision=6
formula.cost.displayPrecision=2
formula.cost.roundingMode=HALF_UP
Formula Calculation Order:
Implement bottom-up rollup with proper sequencing:
// Pseudocode - Correct calculation sequence:
1. Start at leaf components (no child BOMs)
2. For each leaf: extendedCost = unitCost * quantity (6 decimals)
3. Move up one level, aggregate all child costs
4. Add assembly-level costs (labor, overhead)
5. Repeat until reaching top-level product
6. Apply final rounding only at product level
The order matters because:
- Quantity * UnitCost * Overhead ≠ (Quantity * UnitCost) + (Quantity * Overhead) due to rounding
- Calculating leaf-to-root ensures each multiplication happens once
- Summing before multiplying amplifies rounding errors
For your fastener example ($0.00347 × 200 qty):
- Wrong: $0.003 × 200 = $0.60 (rounded too early, $0.094 error)
- Right: $0.00347 × 200 = $0.694 (round to $0.69 at final step)
Implement validation: recalculate 10 products manually in Excel with 6 decimal precision, compare to TC results. Variance should be <$0.01 per product. If larger, trace which formula step introduces error.
After schema changes, run data migration:
UPDATE CostData SET unitCost =
CAST(sourceUnitCost AS DECIMAL(14,6))
WHERE precision < 6;
This comprehensive fix eliminates precision loss and ensures your cost rollups are accurate for pricing decisions.