import '../../../assert/js/assert.js';
import '../../../globals.js';
// Copyright 2014-2022, University of Colorado Boulder
/**
 * A fraction and associated operations.
 *
 * NOTE: The common version of this class in the PhET Java code base has a number of additional methods.  These methods
 * should be ported into this file as needed.  Please see edu.colorado.phet.fractions.common.math.Fraction.java in the
 * PhET Java code base.
 *
 * @author John Blanco
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Sam Reid (PhET Interactive Simulations)
 */
import Utils from '../../../dot/js/Utils.js';
import IOType from '../../../tandem/js/types/IOType.js';
import NumberIO from '../../../tandem/js/types/NumberIO.js';
import phetcommon from '../phetcommon.js';
class Fraction {
    constructor(numerator, denominator) {
        assert && assert(Number.isInteger(numerator), `numerator must be an integer: ${numerator}`);
        assert && assert(Number.isInteger(denominator), `denominator must be an integer: ${denominator}`);
        this._numerator = numerator;
        this._denominator = denominator;
    }
    /**
     * Sets the numerator, which must be an integer.
     */
    set numerator(value) {
        assert && assert(Number.isInteger(value), `numerator must be an integer: ${value}`);
        this._numerator = value;
    }
    /**
     * Gets the numerator.
     */
    get numerator() { return this._numerator; }
    /**
     * Sets the denominator, which must be an integer.
     */
    set denominator(value) {
        assert && assert(Number.isInteger(value), `denominator must be an integer: ${value}`);
        this._denominator = value;
    }
    /**
     * Gets the denominator.
     */
    get denominator() { return this._denominator; }
    /**
     * Computes the numeric value of the fraction.
     * Floating-point error is not an issue as long as numerator and denominator are integers < 2^53.
     */
    getValue() {
        return this.numerator / this.denominator;
    }
    get value() { return this.getValue(); }
    /**
     * Does this fraction reduce to an integer value?
     */
    isInteger() {
        return (this.numerator % this.denominator === 0);
    }
    toString() {
        return `${this.numerator}/${this.denominator}`;
    }
    copy() {
        return new Fraction(this.numerator, this.denominator);
    }
    /**
     * Reduces this fraction, modifies the numerator and denominator.
     */
    reduce() {
        const gcd = Utils.gcd(this.numerator, this.denominator);
        this.numerator = (gcd === 0) ? 0 : Utils.roundSymmetric(this.numerator / gcd);
        this.denominator = (gcd === 0) ? 0 : Utils.roundSymmetric(this.denominator / gcd);
        return this;
    }
    /**
     * Creates a reduced instance of this fraction.
     */
    reduced() {
        return this.copy().reduce();
    }
    /**
     * Is this fraction reduced?
     */
    isReduced() {
        return Utils.gcd(this.numerator, this.denominator) === 1;
    }
    /**
     * Returns whether the two fractions are equal (not whether their reduced values are equal).
     */
    equals(fraction) {
        return (this.numerator === fraction.numerator) && (this.denominator === fraction.denominator);
    }
    /**
     * Returns whether this fraction has a value that is less than the provided fraction.
     */
    isLessThan(fraction) {
        // The more straightforward approach would be: this.getValue() < fraction.getValue().
        // But that uses floating-point operations and comparisons, which could result in a loss of precision.
        // https://github.com/phetsims/phetcommon/issues/43
        return SCRATCH_FRACTION.set(this).subtract(fraction).sign === -1;
    }
    /**
     * Gets the sign of the value, as defined by Math.sign
     */
    get sign() {
        return Math.sign(this.getValue());
    }
    /**
     * Returns the absolute value of this fraction.
     */
    abs() {
        return new Fraction(Math.abs(this.numerator), Math.abs(this.denominator));
    }
    /**
     * Sets the value of this fraction to the provided fraction.
     */
    set(value) {
        this.numerator = value.numerator;
        this.denominator = value.denominator;
        return this;
    }
    /**
     * Sets the value of this fraction to the sum of the two fractions:
     * numerator1 / denominator1 + numerator2 / denominator2
     */
    setToSum(numerator1, denominator1, numerator2, denominator2) {
        assert && assert(Number.isInteger(numerator1), 'numerator1 must be an integer');
        assert && assert(Number.isInteger(denominator1), 'denominator1 must be an integer');
        assert && assert(Number.isInteger(numerator2), 'numerator2 must be an integer');
        assert && assert(Number.isInteger(denominator2), 'denominator2 must be an integer');
        const lcm = Utils.lcm(denominator1, denominator2);
        this.numerator = Utils.roundSymmetric(numerator1 * lcm / denominator1) +
            Utils.roundSymmetric(numerator2 * lcm / denominator2);
        this.denominator = lcm;
        return this;
    }
    /**
     * Adds the provided fraction into this fraction (mutates this fraction). The result is NOT reduced,
     * and has a denominator that is the least-common multiple of the 2 denominators.
     */
    add(value) {
        return this.setToSum(this.numerator, this.denominator, value.numerator, value.denominator);
    }
    /**
     * Adds a fraction to this fraction to create a new fraction.
     * The result is not reduced, and has a denominator that is the least-common multiple of the 2 denominators.
     */
    plus(value) {
        return this.copy().add(value);
    }
    /**
     * Subtracts the provided fraction from this fraction (mutates this fraction). The result is NOT reduced,
     * and has a denominator that is the least-common multiple of the 2 denominators.
     */
    subtract(value) {
        return this.setToSum(this.numerator, this.denominator, -value.numerator, value.denominator);
    }
    /**
     * Subtracts a fraction from this fraction to create a new fraction.
     * The result is not reduced, and has a denominator that is the least-common multiple of the 2 denominators.
     */
    minus(value) {
        return this.copy().subtract(value);
    }
    /**
     * Multiplies the provided fraction and this fraction, setting the result into this fraction (mutates).
     * The value is not reduced.
     */
    multiply(value) {
        this.numerator *= value.numerator;
        this.denominator *= value.denominator;
        return this;
    }
    /**
     * Multiplies this fraction by another fraction to create a new fraction.
     * The result is not reduced.
     */
    times(value) {
        return this.copy().multiply(value);
    }
    /**
     * Divides this fraction by the provided fraction, setting the result into this fraction (mutates).
     * The value is not reduced.
     */
    divide(value) {
        this.numerator *= value.denominator;
        this.denominator *= value.numerator;
        return this;
    }
    /**
     * Divides this fraction by another fraction to create a new fraction.
     * The result is not reduced.
     */
    divided(value) {
        return this.copy().divide(value);
    }
    /**
     * Convenience method.
     * Adds an integer value to this fraction to create a new fraction.
     * The result is not reduced, and the denominator is the same as the denominator of this fraction.
     */
    plusInteger(value) {
        assert && assert(Number.isInteger(value), `value is not an integer: ${value}`);
        return new Fraction(this.numerator + (value * this.denominator), this.denominator);
    }
    /**
     * Convenience method.
     * Subtracts an integer value from this fraction to create a new fraction.
     * The result is not reduced, and the denominator is the same as the denominator of this fraction.
     */
    minusInteger(value) {
        assert && assert(Number.isInteger(value), `value is not an integer: ${value}`);
        return new Fraction(this.numerator - (value * this.denominator), this.denominator);
    }
    /**
     * Convenience method.
     * Multiplies this fraction by an integer to create a new fraction.
     * The result is not reduced, and the denominator is the same as the denominator of this fraction.
     */
    timesInteger(value) {
        assert && assert(Number.isInteger(value), `value is not an integer: ${value}`);
        return new Fraction(this.numerator * value, this.denominator);
    }
    /**
     * Convenience method.
     * Divides this fraction by an integer to create a new fraction.
     * This operation affects the value and sign of the denominator only, and the result is not reduced.
     * Careful! Division by zero is allowed here.
     */
    dividedInteger(value) {
        assert && assert(Number.isInteger(value), `value is not an integer: ${value}`);
        return new Fraction(this.numerator, this.denominator * value);
    }
    /**
     * Convenience method for constructing a fraction from an integer.
     */
    static fromInteger(value) {
        assert && assert(Number.isInteger(value), `value is not an integer: ${value}`);
        return new Fraction(value, 1);
    }
    /**
     * Convert a number into a Fraction
     */
    static fromDecimal(value) {
        if (Number.isInteger(value)) {
            return Fraction.fromInteger(value);
        }
        else {
            // Get the decimal part of the number.
            const decimal = value - Utils.toFixedNumber(value, 0);
            assert && assert(decimal !== 0, 'expected decimal to be non-zero');
            // Convert the decimal part into an integer. This becomes the denominator.
            const denominator = Math.pow(10, Utils.numberOfDecimalPlaces(decimal));
            // Compute numerator
            const numerator = Utils.toFixedNumber(value * denominator, 0);
            return new Fraction(numerator, denominator).reduce();
        }
    }
    /**
     * Serializes this Fraction instance.
     */
    toStateObject() {
        return {
            numerator: this._numerator,
            denominator: this._denominator
        };
    }
    /**
     * Deserializes a Fraction from PhET-iO state.
     */
    static fromStateObject(stateObject) {
        return new Fraction(stateObject.numerator, stateObject.denominator);
    }
}
Fraction.ZERO = new Fraction(0, 1);
Fraction.ONE = new Fraction(1, 1);
/**
 * IOType for Fraction.
 */
Fraction.FractionIO = new IOType('FractionIO', {
    valueType: Fraction,
    stateSchema: {
        numerator: NumberIO,
        denominator: NumberIO
    },
    toStateObject: (fraction) => fraction.toStateObject(),
    fromStateObject: (stateObject) => Fraction.fromStateObject(stateObject)
});
export default Fraction;
// Used to avoid GC. NOTE: Do NOT move in front of the constructor, as it is creating a copy of the type defined.
const SCRATCH_FRACTION = new Fraction(1, 1);
phetcommon.register('Fraction', Fraction);
//# sourceMappingURL=Fraction.js.map