Skip to content

Instantly share code, notes, and snippets.

@jerolan
Created February 29, 2024 18:24
Show Gist options
  • Save jerolan/c018e19210806be35fa6cab5c2240cd1 to your computer and use it in GitHub Desktop.
Save jerolan/c018e19210806be35fa6cab5c2240cd1 to your computer and use it in GitHub Desktop.

Revisions

  1. jerolan created this gist Feb 29, 2024.
    63 changes: 63 additions & 0 deletions Allocate.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,63 @@
    /**
    * From https://v1.dinerojs.com/dinero.js
    * Allocates the amount of a Dinero object according to a list of ratios.
    *
    * Sometimes you need to split monetary values but percentages can't cut it without adding or losing pennies.
    * A good example is invoicing: let's say you need to bill $1,000.03 and you want a 50% downpayment.
    * If you use {@link module:Dinero~percentage percentage}, you'll get an accurate Dinero object but the amount won't be billable: you can't split a penny.
    * If you round it, you'll bill a penny extra.
    * With {@link module:Dinero~allocate allocate}, you can split a monetary amount then distribute the remainder as evenly as possible.
    *
    * You can use percentage style or ratio style for `ratios`: `[25, 75]` and `[1, 3]` will do the same thing.
    *
    * Since v1.8.0, you can use zero ratios (such as [0, 50, 50]). If there's a remainder to distribute, zero ratios are skipped and return a Dinero object with amount zero.
    *
    * @param {Number[]} ratios - The ratios to allocate the money to.
    *
    * @example
    * // returns an array of two Dinero objects
    * // the first one with an amount of 502
    * // the second one with an amount of 501
    * Dinero({ amount: 1003 }).allocate([50, 50])
    * @example
    * // returns an array of two Dinero objects
    * // the first one with an amount of 25
    * // the second one with an amount of 75
    * Dinero({ amount: 100 }).allocate([1, 3])
    * @example
    * // since version 1.8.0
    * // returns an array of three Dinero objects
    * // the first one with an amount of 0
    * // the second one with an amount of 502
    * // the third one with an amount of 501
    * Dinero({ amount: 1003 }).allocate([0, 50, 50])
    *
    * @throws {TypeError} If ratios are invalid.
    *
    * @return {Dinero[]}
    */
    allocate(ratios) {
    assertValidRatios(ratios)

    const total = ratios.reduce((a, b) => calculator.add(a, b))
    let remainder = this.getAmount()

    const shares = ratios.map(ratio => {
    const share = Math.floor(
    calculator.divide(calculator.multiply(this.getAmount(), ratio), total)
    )
    remainder = calculator.subtract(remainder, share)
    return create.call(this, { amount: share })
    })

    let i = 0
    while (remainder > 0) {
    if (ratios[i] > 0) {
    shares[i] = shares[i].add(create.call(this, { amount: 1 }))
    remainder = calculator.subtract(remainder, 1<)
    }
    i += 1
    }

    return shares
    }