/*******************************************************************************
* Copyright (c) 2009-2013 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* * Arnold Lankamp - interfaces and implementation
* * Jurgen Vinju - extensions and fixes
* * Davy Landman - added mathematical functions
* * Paul Klint - Precision handling
* * Michael Steindorfer - Michael.Steindorfer@cwi.nl - CWI
*******************************************************************************/
package org.rascalmpl.value.impl.primitive;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import org.rascalmpl.value.IBool;
import org.rascalmpl.value.IInteger;
import org.rascalmpl.value.INumber;
import org.rascalmpl.value.IRational;
import org.rascalmpl.value.IReal;
import org.rascalmpl.value.IValue;
import org.rascalmpl.value.impl.util.BigDecimalCalculations;
import org.rascalmpl.value.type.Type;
import org.rascalmpl.value.type.TypeFactory;
import org.rascalmpl.value.visitors.IValueVisitor;
/*
* TODO: provide specializations for smaller values, similar to IntegerValue / BigIntegerValue.
*/
/*package*/ class BigDecimalValue extends AbstractNumberValue implements IReal {
private final static Type DOUBLE_TYPE = TypeFactory.getInstance().realType();
protected final BigDecimal value;
/*package*/ static IReal newReal(BigDecimal value) {
return new BigDecimalValue(value);
}
/*package*/ static IReal newReal(String value) {
return new BigDecimalValue(new BigDecimal(value));
}
/*package*/ static IReal newReal(String value, int precision) throws NumberFormatException {
return new BigDecimalValue(new BigDecimal(value, new MathContext(precision)));
}
/*package*/ static IReal newReal(double value) {
checkNanAndInfinity(value);
return new BigDecimalValue(BigDecimal.valueOf(value));
}
public static void checkNanAndInfinity(double value) {
if (Double.isNaN(value)) {
throw new NumberFormatException("no support for NaN");
}
if (Double.isInfinite(value)) {
throw new NumberFormatException("no support for infinity");
}
}
/*package*/ static IReal newReal(double value, int precision) {
checkNanAndInfinity(value);
return new BigDecimalValue(new BigDecimal(value, new MathContext(precision)));
}
private BigDecimalValue(BigDecimal value){
super();
this.value = value;
}
private BigDecimalValue(BigDecimal value, int precision){
super();
this.value = new BigDecimal(value.toEngineeringString(), new MathContext(precision));
}
@Override
public IReal abs() {
return newReal(value.abs());
}
@Override
public IReal toReal(int precision) {
return this;
}
@Override
public Type getType(){
return DOUBLE_TYPE;
}
@Override
public float floatValue(){
return value.floatValue();
}
@Override
public double doubleValue(){
return value.doubleValue();
}
@Override
public IInteger toInteger(){
return IntegerValue.newInteger(value.toBigInteger());
}
@Override
public IRational toRational(){
throw new UnsupportedOperationException();
}
@Override
public IReal floor(){
return BigDecimalValue.newReal(value.setScale(0, RoundingMode.FLOOR));
}
@Override
public IReal round(){
return BigDecimalValue.newReal(value.setScale(0, RoundingMode.HALF_UP));
}
@Override
public IReal add(IReal other){
return BigDecimalValue.newReal(value.add(((BigDecimalValue) other).value));
}
@Override
public INumber add(IInteger other) {
return add(other.toReal(value.precision()));
}
@Override
public INumber add(IRational other) {
return add(other.toReal(value.precision()));
}
@Override
public IReal subtract(IReal other){
return BigDecimalValue.newReal(value.subtract(((BigDecimalValue) other).value));
}
@Override
public INumber subtract(IInteger other) {
return subtract(other.toReal(value.precision()));
}
@Override
public INumber subtract(IRational other) {
return subtract(other.toReal(value.precision()));
}
@Override
public IReal multiply(IReal other){
//int precision = Math.min(Math.max(value.precision(), other.precision()), BaseValueFactory.PRECISION);
//MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
return BigDecimalValue.newReal(value.multiply(((BigDecimalValue) other).value));
}
@Override
public INumber multiply(IInteger other) {
return multiply(other.toReal(value.precision()));
}
@Override
public INumber multiply(IRational other) {
return multiply(other.toReal(value.precision()));
}
@Override
public IReal divide(IReal other, int precision){
// make sure the precision is *at least* the same as that of the arguments
precision = Math.max(Math.max(value.precision(), other.precision()), precision);
MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
return BigDecimalValue.newReal(value.divide(((BigDecimalValue) other).value, mc));
}
@Override
public IReal divide(IInteger other, int precision) {
return divide(other.toReal(precision), precision);
}
@Override
public IReal divide(IRational other, int precision) {
return divide(other.toReal(precision), precision);
}
@Override
public IReal negate(){
return BigDecimalValue.newReal(value.negate());
}
@Override
public int precision(){
return value.precision();
}
@Override
public int scale(){
return value.scale();
}
@Override
public IInteger unscaled(){
return IntegerValue.newInteger(value.unscaledValue());
}
@Override
public IBool equal(IReal other){
return BoolValue.getBoolValue(compare(other) == 0);
}
@Override
public IBool equal(IInteger other) {
return equal(other.toReal(value.precision()));
}
@Override
public IBool equal(IRational other) {
return equal(other.toReal(value.precision()));
}
@Override
public IBool greater(IReal other){
return BoolValue.getBoolValue(compare(other) > 0);
}
@Override
public IBool greater(IInteger other) {
return greater(other.toReal(value.precision()));
}
@Override
public IBool greater(IRational other) {
return greater(other.toReal(value.precision()));
}
@Override
public IBool greaterEqual(IReal other){
return BoolValue.getBoolValue(compare(other) >= 0);
}
@Override
public IBool greaterEqual(IInteger other) {
return greaterEqual(other.toReal(value.precision()));
}
@Override
public IBool greaterEqual(IRational other) {
return greaterEqual(other.toReal(value.precision()));
}
@Override
public IBool less(IReal other){
return BoolValue.getBoolValue(compare(other) < 0);
}
@Override
public IBool less(IInteger other) {
return less(other.toReal(value.precision()));
}
@Override
public IBool less(IRational other) {
return less(other.toReal(value.precision()));
}
@Override
public IBool lessEqual(IReal other){
return BoolValue.getBoolValue(compare(other) <= 0);
}
@Override
public IBool lessEqual(IInteger other) {
return lessEqual(other.toReal(value.precision()));
}
@Override
public IBool lessEqual(IRational other) {
return lessEqual(other.toReal(value.precision()));
}
@Override
public int compare(IReal other){
return value.compareTo(((BigDecimalValue) other).value);
}
@Override
public int compare(INumber other) {
return compare(other.toReal(value.precision()));
}
@Override
public <T, E extends Throwable> T accept(IValueVisitor<T, E> v) throws E {
return v.visitReal(this);
}
/*
* Description and implementation from the (now removed) reference implementation:
*
* // Java BigDecimals have a bug, their even though 3.0 and 3.00 are equal,
* // their hashCode() is not, which is against the equals/hashCode() contract.
* // To work around this, we use this simple trick here which is correct but
* // might lead to many collisions.
* // return Double.valueOf(value.doubleValue()).hashCode();
*/
public int hashCode(){
// BigDecimals don't generate consistent hashcodes for things that are actually 'equal'.
// This code rectifies this problem.
long bits = Double.doubleToLongBits(value.doubleValue());
return (int) (bits ^ (bits >>> 32));
}
public boolean equals(Object o){
if(o == null) return false;
if(o.getClass() == getClass()){
BigDecimalValue otherDouble = (BigDecimalValue) o;
return (value.equals(otherDouble.value));
}
return false;
}
@Override
public boolean isEqual(IValue o){
return equals(o);
}
@Override
public String getStringRepresentation(){
StringBuilder sb = new StringBuilder();
String decimalString = value.toString();
sb.append(decimalString);
if (!decimalString.matches(".*[\\.Ee].*")) {
sb.append(".");
}
return sb.toString();
}
@Override
public int signum() {
return value.signum();
}
@Override
public IReal log(IInteger base, int precision) {
return log(base.toReal(precision), precision);
}
@Override
public IReal log(IReal base, int precision) {
IReal lnBase = base.ln(precision + 1);
IReal lnThis = this.ln(precision + 1);
return lnThis.divide(lnBase, precision);
}
@Override
public IReal ln(int precision) {
return newReal(BigDecimalCalculations.ln(value, precision));
}
@Override
public IReal sqrt(int precision) {
return newReal(BigDecimalCalculations.sqrt(value, precision));
}
@Override
public IReal nroot(IInteger n, int precision) {
return newReal(BigDecimalCalculations.intRoot(value, new BigInteger(n.getTwosComplementRepresentation()), precision));
}
@Override
public IReal exp(int precision) {
return newReal(BigDecimalCalculations.exp(value, precision));
}
@Override
public IReal pow(IInteger power) {
if (power.signum() < 0) {
// negative power is 1/(this^-power)
return newReal(
BigDecimal.ONE.divide(value.pow(power.negate().intValue()), value.precision(), RoundingMode.HALF_EVEN)
);
}
return newReal(value.pow(power.intValue()));
}
@Override
public IReal pow(IReal power, int precision) {
BigDecimal actualPower = null;
if (power instanceof BigDecimalValue) {
actualPower = ((BigDecimalValue)power).value;
}
else {
actualPower = new BigDecimal(power.getStringRepresentation());
}
return newReal(BigDecimalCalculations.pow(value, actualPower, precision));
}
@Override
public IReal tan(int precision) {
return newReal(BigDecimalCalculations.tan(value, precision));
}
@Override
public IReal sin(int precision) {
return newReal(BigDecimalCalculations.sin(value, precision));
}
@Override
public IReal cos(int precision) {
return newReal(BigDecimalCalculations.cos(value, precision));
}
public static IReal pi(int precision) {
if (precision < 0 || precision > 1000)
throw new IllegalArgumentException("PI max precision is 1000");
return newReal(BigDecimalCalculations.PI.setScale(precision, BigDecimal.ROUND_HALF_EVEN));
}
public static IReal e(int precision) {
if (precision < 0 || precision > 1000)
throw new IllegalArgumentException("E max precision is 1000");
return newReal(BigDecimalCalculations.E.setScale(precision, BigDecimal.ROUND_HALF_EVEN));
}
}