DEV Community

Cover image for Precise Allocations with Big.js: Handling Rounding and Leftover Redistribution
Piyush Chauhan
Piyush Chauhan

Posted on

Precise Allocations with Big.js: Handling Rounding and Leftover Redistribution

When dealing with fractional allocations of large amounts, rounding errors and leftover redistributions become significant challenges. These problems are not confined to financial calculations; they can occur in other domains like resource distribution, task scheduling, or budget allocation. In this article, we demonstrate a verified and tested method using the big.js library in JavaScript to achieve precise allocations while handling rounding and leftover redistribution effectively.


Problem: Allocating Funds Among Stocks

Imagine a scenario where you need to allocate a very large amount of money among multiple stocks based on their respective percentages. For example:

  • Stock A: 50.5%
  • Stock B: 30.3%
  • Stock C: 19.2%

The requirements are:

  • Perform calculations in cents to avoid floating-point errors.
  • Distribute leftover cents fairly after initial rounding.
  • Convert the final allocations back to dollars with two decimal places.

The Solution

Using the big.js library, we can handle these challenges with arbitrary-precision arithmetic. Here is the complete solution:

1. Initialize Inputs and Convert Percentages to Ratios

const Big = require("big.js");

function allocateMoney(amount, allocations) {
  // Step 1: Convert percentages to rational numbers
  let totalPercent = new Big(0);
  for (let key in allocations) {
    totalPercent = totalPercent.plus(new Big(allocations[key]));
  }

  const allocationRatios = {};
  for (let key in allocations) {
    allocationRatios[key] = new Big(allocations[key]).div(totalPercent);
  }
Enter fullscreen mode Exit fullscreen mode

2. Calculate Initial Allocation in Cents

Convert the total amount to cents and perform initial rounding:

  const totalCents = new Big(amount).times(100).toFixed(0); // Convert amount to cents
  const allocatedCents = {};
  for (let key in allocationRatios) {
    allocatedCents[key] = allocationRatios[key].times(totalCents).toFixed(0, 0); // Convert to int (round down)
  }
Enter fullscreen mode Exit fullscreen mode

3. Redistribute Remaining Cents

Calculate the leftover cents and distribute them fairly based on fractional remainders:

  let distributedTotal = new Big(0);
  for (let key in allocatedCents) {
    distributedTotal = distributedTotal.plus(new Big(allocatedCents[key]));
  }

  const remainingCents = new Big(totalCents).minus(distributedTotal).toFixed(0);

  // Sort allocations by fractional remainder descending for redistribution
  const fractionalRemainders = {};
  for (let key in allocationRatios) {
    const allocated = allocationRatios[key].times(totalCents);
    const fractionalPart = allocated.minus(allocated.toFixed(0));
    fractionalRemainders[key] = fractionalPart;
  }

  const sortedKeys = Object.keys(fractionalRemainders).sort((a, b) => {
    if (fractionalRemainders[b].gt(fractionalRemainders[a])) {
      return 1;
    }
    if (fractionalRemainders[b].lt(fractionalRemainders[a])) {
      return -1;
    }
    return 0;
  });

  for (let i = 0; i < remainingCents; i++) {
    const key = sortedKeys[i % sortedKeys.length];
    allocatedCents[key] = new Big(allocatedCents[key]).plus(1).toFixed(0);
  }
Enter fullscreen mode Exit fullscreen mode

4. Convert Back to Dollars

Finally, convert the allocations back to dollars:

  const allocatedDollars = {};
  for (let key in allocatedCents) {
    allocatedDollars[key] = new Big(allocatedCents[key]).div(100).toFixed(2); // Convert cents to dollars with 2 decimals
  }

  return allocatedDollars;
}
Enter fullscreen mode Exit fullscreen mode

Example Usage

Here’s how you can use the allocateMoney function to allocate funds among stocks:

const totalAmount = "1234567890123456.78"; // A very large total amount
const stockAllocations = {
  "Stock A": "50.5", // 50.5%
  "Stock B": "30.3", // 30.3%
  "Stock C": "19.2", // 19.2%
};

const result = allocateMoney(totalAmount, stockAllocations);
console.log("Allocation:");
console.log(result);

// Calculate total allocated
let totalAllocated = new Big(0);
for (let key in result) {
  totalAllocated = totalAllocated.plus(new Big(result[key]));
}

console.log(`Total Allocated: $${totalAllocated.toFixed(2)}`);
Enter fullscreen mode Exit fullscreen mode

Output for the Example

For the given inputs, the output is:

Allocation:
{
  'Stock A': '623456784512345.67',
  'Stock B': '374074070707407.41',
  'Stock C': '237037034903703.70'
}
Total Allocated: $1234567890123456.78
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Precise Arithmetic with big.js:
    The big.js library ensures accuracy by avoiding floating-point errors.

  2. Handle Leftovers Fairly:
    Use fractional remainders to distribute leftover units deterministically and fairly.

  3. Reconcile Totals:
    After all adjustments, ensure that the total allocation matches the original amount.

  4. Scalable for Large Values:
    This approach works seamlessly for very large amounts, making it suitable for financial and resource allocation problems.

By following this method, you can achieve precise and fair allocations in any scenario requiring high numerical accuracy.

Top comments (0)

typescript

11 Tips That Make You a Better Typescript Programmer

1 Think in {Set}

Type is an everyday concept to programmers, but it’s surprisingly difficult to define it succinctly. I find it helpful to use Set as a conceptual model instead.

#2 Understand declared type and narrowed type

One extremely powerful typescript feature is automatic type narrowing based on control flow. This means a variable has two types associated with it at any specific point of code location: a declaration type and a narrowed type.

#3 Use discriminated union instead of optional fields

...

Read the whole post now!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay