All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sri.ai.util.math.MixedRadixNumber Maven / Gradle / Ivy

/*
 * Copyright (c) 2013, SRI International
 * All rights reserved.
 * Licensed under the The BSD 3-Clause License;
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 * 
 * http://opensource.org/licenses/BSD-3-Clause
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * Neither the name of the aic-util nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.sri.ai.util.math;

import java.math.BigInteger;
import java.util.List;

import com.google.common.annotations.Beta;

/**
 * For details on Mixed Radix Number Representations.
 * 
 * Will proceed from right to left in sequence based on the radix values provided.
 * 
 * @author Ciaran O'Reilly
 */
@Beta
public class MixedRadixNumber extends Number {
	//
	private static final long serialVersionUID = 1L;
	//
	private BigInteger value = BigInteger.ZERO;
	private BigInteger maxValue = BigInteger.ZERO;
	private int[] radices = null;
	private int[] currentNumeralValue = null;
	private BigInteger[] cachedRadixValues = null;
	private boolean recalculateValue = false;

	/**
	 * Constructs a mixed radix number with a specified value and a specified
	 * array of radices.
	 * 
	 * @param value
	 *            the value of the mixed radix number
	 * @param radices
	 *            the radices used to represent the value of the mixed radix
	 *            number
	 */
	public MixedRadixNumber(BigInteger value, int[] radices) {
		this.value = value;
		this.radices = new int[radices.length];
		System.arraycopy(radices, 0, this.radices, 0, radices.length);
		initialize();
	}

	/**
	 * Constructs a mixed radix number with a specified value and a specified
	 * list of radices.
	 * 
	 * @param value
	 *            the value of the mixed radix number
	 * @param radices
	 *            the radices used to represent the value of the mixed radix
	 *            number
	 */
	public MixedRadixNumber(BigInteger value, List radices) {
		this.value = value;
		this.radices = new int[radices.size()];
		for (int i = 0; i < radices.size(); i++) {
			this.radices[i] = radices.get(i);
		}
		initialize();
	}
	
	/**
	 * Constructs a mixed radix number with a specified array of numerals and a
	 * specified array of radices.
	 * 
	 * @param radixValues
	 *            the numerals of the mixed radix number
	 * @param radices
	 *            the radices of the mixed radix number
	 */
	public MixedRadixNumber(List radixValues, List radices) {
		this.radices = new int[radices.size()];
		for (int i = 0; i < radices.size(); i++) {
			this.radices[i] = radices.get(i);
		}
		initialize();
		int[] radValues = new int[radixValues.size()];
		for (int i = 0; i < radValues.length; i++) {
			radValues[i] = radixValues.get(i);
		}
		setCurrentValueFor(radValues);
	}

	/**
	 * Constructs a mixed radix number with a specified array of numerals and a
	 * specified array of radices.
	 * 
	 * @param radixValues
	 *            the numerals of the mixed radix number
	 * @param radices
	 *            the radices of the mixed radix number
	 */
	public MixedRadixNumber(int[] radixValues, int[] radices) {
		this(BigInteger.ZERO, radices);
		setCurrentValueFor(radixValues);
	}

	/**
	 * 
	 * @return return the current value of the mixed radix number as a
	 *         BigInteger.
	 */
	public BigInteger getValue() {
		if (recalculateValue) {
			value = getValueFor(currentNumeralValue);
			recalculateValue = false;
		}
		return value;
	}

	/**
	 * Returns the maximum value which can be represented by the current array
	 * of radices.
	 * 
	 * @return the maximum value which can be represented by the current array
	 *         of radices.
	 */
	public BigInteger getMaxAllowedValue() {
		return maxValue;
	}

	/**
	 * Returns the value of the mixed radix number with the specified array of
	 * numerals and the current array of radices.
	 * 
	 * @param radixValues
	 *            the current radix values.
	 * 
	 * @return the value of the mixed radix number
	 * 
	 * @throws IllegalArgumentException
	 *             if any of the specified numerals is less than zero, or if any
	 *             of the specified numerals is greater than it's corresponding
	 *             radix.
	 */
	public BigInteger getValueFor(int[] radixValues) {
		if (radixValues.length != radices.length) {
			throw new IllegalArgumentException(
					"Radix values not same size as Radices.");
		}

		BigInteger cvalue = BigInteger.ZERO;
		BigInteger mvalue = BigInteger.ONE;
		for (int i = radixValues.length-1; i >= 0; i--) {
			if (radixValues[i] < 0 || radixValues[i] >= radices[i]) {
				throw new IllegalArgumentException("Radix numeral value " + i
						+ " is out of range for radix at this position");
			}
			if (i != radixValues.length-1) {
				mvalue = mvalue.multiply(cachedRadixValues[radices[i + 1]]);
			}
			cvalue = cvalue.add(mvalue
					.multiply(cachedRadixValues[radixValues[i]]));
		}
		return cvalue;
	}

	/**
	 * Sets the value of the mixed radix number with the specified array of
	 * numerals and the current array of radices.
	 * 
	 * @param radixValues
	 *            the numerals of the mixed radix number
	 */
	public void setCurrentValueFor(int[] radixValues) {
		this.value = getValueFor(radixValues);
		System.arraycopy(radixValues, 0, this.currentNumeralValue, 0,
				radixValues.length);
	}

	/**
	 * 
	 * @return true if you can increment by 1 the current value, false
	 *         otherwise.
	 */
    public boolean canIncrement() {
        boolean canIncrement = false;
        for (int i = 0; i < radices.length; i++) {
            if (currentNumeralValue[i] != radices[i] - 1) {
                canIncrement = true;
                break;
            }
        }

        return canIncrement;
    }

	/**
	 * Increments the value of the mixed radix number, if the value is less than
	 * the maximum value which can be represented by the current array of
	 * radices.
	 * 
	 * @return true if the increment was successful.
	 */
	public boolean increment() {
		boolean canIncrement = canIncrement();

		if (canIncrement) {
			for (int i = radices.length-1; i >= 0; i--) {
				if (currentNumeralValue[i] < radices[i] - 1) {
					currentNumeralValue[i] = currentNumeralValue[i] + 1;
					for (int j = i + 1; j < radices.length; j++) {
						currentNumeralValue[j] = 0;
					}
					break;
				}
			}
			recalculateValue = true;
		}

		return canIncrement;
	}

	/**
	 * 
	 * @return true if you can decrement by 1 the current value, false
	 *         otherwise.
	 */
	public boolean canDecrement() {
		boolean canDecrement = false;
		for (int i = radices.length-1; i >= 0; i--) {
	        if (currentNumeralValue[i] != 0) {
	            canDecrement = true;
	            break;
	        }
	    }

    	return canDecrement;
	}

	/**
	 * Decrements the value of the mixed radix number, if the value is greater
	 * than zero.
	 * 
	 * @return true if the decrement was successful.
	 */
	public boolean decrement() {
		boolean canDecrement = canDecrement();

		if (canDecrement) {
			for (int i = radices.length-1; i >= 0; i--) {
				if (currentNumeralValue[i] > 0) {
					currentNumeralValue[i] = currentNumeralValue[i] - 1;
					for (int j = i + 1; j < radices.length; j++) {
						currentNumeralValue[j] = radices[j] - 1;
					}
					break;
				}
			}
			recalculateValue = true;
		}

		return canDecrement;
	}

	/**
	 * Returns the numeral at the specified position.
	 * 
	 * @param atPosition
	 *            the position of the numeral to return
	 * @return the numeral at the specified position.
	 */
	public int getCurrentNumeralValue(int atPosition) {
		if (atPosition >= 0 && atPosition < radices.length) {
			return currentNumeralValue[atPosition];
		}
		throw new IllegalArgumentException(
				"Argument atPosition must be >=0 and < " + radices.length);
	}

	//
	// START-Number
	@Override
	public int intValue() {
		return getValue().intValue();
	}

	@Override
	public long longValue() {
		return getValue().longValue();
	}

	@Override
	public float floatValue() {
		return getValue().floatValue();
	}

	@Override
	public double doubleValue() {
		return getValue().doubleValue();
	}

	// END-Number
	//

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		for (int i = 0; i < radices.length; i++) {
			sb.append("[");
			sb.append(this.getCurrentNumeralValue(i));
			sb.append("]");
		}

		return sb.toString();
	}

	//
	// PRIVATE
	//

	/**
	 * Sets the maximum value which can be represented by the current array of
	 * radices and related working attributes.
	 * 
	 * @throws IllegalArgumentException
	 *             if no radices are defined, if any radix is less than one, or
	 *             if the current value is greater than the maximum value which
	 *             can be represented by the current array of radices.
	 */
	private void initialize() {
		if (0 == radices.length) {
			throw new IllegalArgumentException(
					"At least 1 radix must be defined.");
		}
		for (int i = 0; i < radices.length; i++) {
			if (radices[i] < 1) {
				throw new IllegalArgumentException(
						"Invalid radix, must be >= 1");
			}
		}

		// Cache the possible radix values
		// so we don't have to create BigIntegers
		// for the same set of values multiple times
		int maxRadixValue = 0;
		for (int i = 0; i < radices.length; i++) {
			if (radices[i] > maxRadixValue) {
				maxRadixValue = radices[i];
			}
		}
		cachedRadixValues = new BigInteger[maxRadixValue + 1];
		for (int i = 0; i <= maxRadixValue; i++) {
			cachedRadixValues[i] = BigInteger.valueOf(i);
		}

		// Calculate the maxValue allowed
		maxValue = cachedRadixValues[radices[0]];
		for (int i = 1; i < radices.length; i++) {
			maxValue = maxValue.multiply(cachedRadixValues[radices[i]]);
		}
		maxValue = maxValue.subtract(BigInteger.ONE);

		if (value.max(maxValue) != maxValue && !value.equals(maxValue)) {
			throw new IllegalArgumentException(
					"The value ["
							+ value
							+ "] cannot be represented with the radices provided, max value is "
							+ maxValue);
		}

		currentNumeralValue = new int[radices.length];
		// Now set the current numeral values based on
		// the current value of the mixed radix number
		BigInteger quotient = value;
		for (int i = radices.length -1; i >= 0; i--) {
			if (!quotient.equals(BigInteger.ZERO)) {
				currentNumeralValue[i] = quotient.mod(
						cachedRadixValues[radices[i]]).intValue();
				quotient = quotient.divide(cachedRadixValues[radices[i]]);
			} 
			else {
				currentNumeralValue[i] = 0;
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy