/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2006 - 2016 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.formula.util;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.LibFormulaBoot;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import java.math.BigDecimal;
public class NumberUtil {
public static final BigDecimal DELTA = new BigDecimal( "0.000000000000000000000000000000000000005" );
public static final BigDecimal MINUTE_ROUNDING_DELTA = new BigDecimal( "0.00000000000000000000000000000000000009" );
public static final BigDecimal INT_TEST_DELTA = new BigDecimal( "0.00000000000000000000000000000000005" );
private static final int ROUND_SCALE = LibFormulaBoot.GLOBAL_SCALE - 6;
private NumberUtil() {
}
public static BigDecimal getAsBigDecimal( final Number number ) {
if ( number == null ) {
throw new NullPointerException();
}
if ( number instanceof BigDecimal ) {
return (BigDecimal) number;
} else {
return new BigDecimal( number.toString() );
}
}
/**
* Performs a rounding to get a more reliable (int) cast. This makes sure that nearly exact values like
* 0.9999999..9999 are correctly interpreted as 1 while exact values like 0.99 are interpreted as 0.
*
* @param n
* @return
*/
public static BigDecimal performIntRounding( BigDecimal n ) {
try {
// no need to go further if the value is already an integer
return n.setScale( 0 );
} catch ( ArithmeticException e ) {
//ignore and continue
}
final BigDecimal round;
if ( n.signum() < 0 ) {
n = n.subtract( DELTA );
return n.setScale( 0, BigDecimal.ROUND_UP );
} else {
n = n.add( DELTA );
round = n.setScale( 1, BigDecimal.ROUND_DOWN );
return round.setScale( 0, BigDecimal.ROUND_DOWN );
}
}
/**
* Performs a rounding to get a more reliable (int) cast for minute function {@link
* org.pentaho.reporting.libraries.formula.function.datetime.MinuteFunction}. See {@link
* org.pentaho.reporting.libraries.formula.util.NumberUtilTest#testPerformMinuteRounding()} for more information.
*
* @param n value of the {@code BigDecimal} to be rounded
* @return a {@code BigDecimal} rounded value
* @see org.pentaho.reporting.libraries.formula.function.datetime.MinuteFunctionTest.java
*/
public static BigDecimal performMinuteRounding( BigDecimal n ) {
try {
// no need to go further if the value is already an integer
return n.setScale( 0 );
} catch ( ArithmeticException e ) {
//ignore and continue
}
n = n.add( MINUTE_ROUNDING_DELTA );
return n.setScale( 0, BigDecimal.ROUND_DOWN );
}
public static BigDecimal performTuneRounding( BigDecimal n ) {
try {
// no need to go further if the value is already an integer
n.setScale( ROUND_SCALE );
return n;
} catch ( ArithmeticException e ) {
//ignore and continue
}
final BigDecimal round;
if ( n.signum() < 0 ) {
n = n.subtract( INT_TEST_DELTA );
round = n.setScale( ROUND_SCALE, BigDecimal.ROUND_UP );
} else {
n = n.add( INT_TEST_DELTA );
round = n.setScale( ROUND_SCALE, BigDecimal.ROUND_DOWN );
}
if ( n.compareTo( round ) == 0 ) {
return n;
}
return NumberUtil.removeTrailingZeros( round );
}
public static BigDecimal removeTrailingZeros( final BigDecimal bd ) {
if ( bd.signum() == 0 ) {
return bd.setScale( 0 );
}
final String text = bd.toPlainString(); // get non-logarithm representation
int scale = bd.scale();
for ( int i = text.length() - 1; i >= 0; i-- ) {
final char c = text.charAt( i );
if ( c != '0' ) {
break;
}
scale -= 1;
}
return bd.setScale( scale );
}
public static BigDecimal divide( final BigDecimal bd1, final BigDecimal bd2 )
throws EvaluationException {
if ( bd2.signum() == 0 ) {
// prevent a division by zero ..
throw EvaluationException.getInstance( LibFormulaErrorValue.ERROR_ARITHMETIC_VALUE );
}
final BigDecimal divide = bd1.divide( bd2, LibFormulaBoot.GLOBAL_SCALE, BigDecimal.ROUND_HALF_UP );
return NumberUtil.removeTrailingZeros( divide );
}
}