<commit_before>'use strict';

const Big = require('big.js');
const l10n = require('./l10n');

let iso4217 = /^[A-Z]{3}$/,
    roundingMode = 2, // ROUND_HALF_EVEN, banker's rounding
    zero = new Big(0);

function Money(amount, currency) {
    // Allow new Money(json)
    if (arguments.length === 1) {
        let o = amount;
        amount = o.amount;
        currency = o.currency;
    }

    // Validation
    amount = new Big(amount);
    if (!iso4217.test(currency)) {
        throw new Error(`'${currency}' is not a valid ISO-4217 currency code`);
    }

    this.amount = Object.freeze(amount);
    this.currency = currency;

    Object.freeze(this);
}

Money.prototype.precision = function precision() {
    let options = l10n(this.currency).resolvedOptions();
    return options.maximumFractionDigits;
};

Money.prototype.plus = function plus(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return new Money(this.amount.plus(that.amount), this.currency);
};

Money.prototype.minus = function plus(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return new Money(this.amount.minus(that.amount), this.currency);
};

Money.prototype.times = function times(that) {
    if (typeof that !== 'number') {
        throw new TypeError('Money multiplication needs a Number');
    }
    return new Money(this.amount.times(that), this.currency);
};

Money.prototype.allocate = function allocate(ratios) {
    if (!Array.isArray(ratios)) {
        throw new TypeError('Money allocation needs an Array');
    }
    if (ratios.length < 1) {
        throw new TypeError('Money allocation needs a non-empty Array');
    }

    let total = ratios.reduce((a,b) => a + b, 0),
        amount = this.round(),
        remainder = amount,
        shares = ratios.map(ratio => {
            let share = amount.times(ratio / total).round();
            remainder = remainder.minus(share);
            return share;
        })
    ;

    if (remainder.isNotZero()) {
        shares[0] = shares[0].plus(remainder).round();
    }

    return shares;
};

Money.prototype.round = function round() {
    return new Money(this.amount.round(this.precision(), roundingMode), this.currency);
};

Money.prototype.toString = function toString() {
    return this.amount.toString() + ' ' + this.currency;
};

Money.prototype.toLocaleString = function toLocaleString(lang) {
    return l10n(lang, this.currency).format(Number(this.amount));
};

Money.prototype.compare = function compare(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return this.amount.cmp(that.amount);
};

Money.prototype.eq = function eq(that) {
    return this.compare(that) === 0;
};<commit_msg>fix(money): round before formatting<commit_after>'use strict';

const Big = require('big.js');
const l10n = require('./l10n');

let iso4217 = /^[A-Z]{3}$/,
    roundingMode = 2, // ROUND_HALF_EVEN, banker's rounding
    zero = new Big(0);

function Money(amount, currency) {
    // Allow new Money(json)
    if (arguments.length === 1) {
        let o = amount;
        amount = o.amount;
        currency = o.currency;
    }

    // Validation
    amount = new Big(amount);
    if (!iso4217.test(currency)) {
        throw new Error(`'${currency}' is not a valid ISO-4217 currency code`);
    }

    this.amount = Object.freeze(amount);
    this.currency = currency;

    Object.freeze(this);
}

Money.prototype.precision = function precision() {
    let options = l10n(this.currency).resolvedOptions();
    return options.maximumFractionDigits;
};

Money.prototype.plus = function plus(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return new Money(this.amount.plus(that.amount), this.currency);
};

Money.prototype.minus = function plus(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return new Money(this.amount.minus(that.amount), this.currency);
};

Money.prototype.times = function times(that) {
    if (typeof that !== 'number') {
        throw new TypeError('Money multiplication needs a Number');
    }
    return new Money(this.amount.times(that), this.currency);
};

Money.prototype.allocate = function allocate(ratios) {
    if (!Array.isArray(ratios)) {
        throw new TypeError('Money allocation needs an Array');
    }
    if (ratios.length < 1) {
        throw new TypeError('Money allocation needs a non-empty Array');
    }

    let total = ratios.reduce((a,b) => a + b, 0),
        amount = this.round(),
        remainder = amount,
        shares = ratios.map(ratio => {
            let share = amount.times(ratio / total).round();
            remainder = remainder.minus(share);
            return share;
        })
    ;

    if (remainder.isNotZero()) {
        shares[0] = shares[0].plus(remainder).round();
    }

    return shares;
};

Money.prototype.round = function round() {
    return new Money(this.amount.round(this.precision(), roundingMode), this.currency);
};

Money.prototype.toString = function toString() {
    return this.amount.toString() + ' ' + this.currency;
};

Money.prototype.toLocaleString = function toLocaleString(lang) {
    return l10n(lang, this.currency).format(Number(this.round().amount));
};

Money.prototype.compare = function compare(that) {
    if (this.currency !== that.currency) {
        throw new Error('Currencies must be the same');
    }

    return this.amount.cmp(that.amount);
};

Money.prototype.eq = function eq(that) {
    return this.compare(that) === 0;
};
{'fraction_non_alphanumeric': 0.14594972067039105, 'fraction_numerical': 0.010125698324022346, 'mean_word_length': 2.9812369701181374, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4641093', 'n_tokens_mistral': 1803, 'n_tokens_neox': 1710, 'n_words': 415}