001/** 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2015-2016 decimal4j (tools4j), Marco Terzer 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024package org.decimal4j.util; 025 026import java.math.RoundingMode; 027import java.util.Objects; 028 029import org.decimal4j.api.DecimalArithmetic; 030import org.decimal4j.scale.ScaleMetrics; 031import org.decimal4j.scale.Scales; 032 033/** 034 * Utility class to round double values to an arbitrary decimal precision between 0 and 18. The rounding is efficient 035 * and garbage free. 036 */ 037public final class DoubleRounder { 038 039 private final ScaleMetrics scaleMetrics; 040 private final double ulp; 041 042 /** 043 * Creates a rounder for the given decimal precision. 044 * 045 * @param precision 046 * the decimal rounding precision, must be in {@code [0,18]} 047 * @throws IllegalArgumentException 048 * if precision is negative or larger than 18 049 */ 050 public DoubleRounder(int precision) { 051 this(toScaleMetrics(precision)); 052 } 053 054 /** 055 * Creates a rounder with the given scale metrics defining the decimal precision. 056 * 057 * @param scaleMetrics 058 * the scale metrics determining the rounding precision 059 * @throws NullPointerException 060 * if scale metrics is null 061 */ 062 public DoubleRounder(ScaleMetrics scaleMetrics) { 063 this.scaleMetrics = Objects.requireNonNull(scaleMetrics, "scaleMetrics cannot be null"); 064 this.ulp = scaleMetrics.getRoundingHalfEvenArithmetic().toDouble(1); 065 } 066 067 /** 068 * Returns the precision of this rounder, a value between zero and 18. 069 * 070 * @return this rounder's decimal precision 071 */ 072 public int getPrecision() { 073 return scaleMetrics.getScale(); 074 } 075 076 /** 077 * Rounds the given double value to the decimal precision of this rounder using {@link RoundingMode#HALF_UP HALF_UP} 078 * rounding. 079 * 080 * @param value 081 * the value to round 082 * @return the rounded value 083 * @see #getPrecision() 084 */ 085 public double round(double value) { 086 return round(value, scaleMetrics.getDefaultArithmetic(), scaleMetrics.getRoundingHalfEvenArithmetic(), ulp); 087 } 088 089 /** 090 * Rounds the given double value to the decimal precision of this rounder using the specified rounding mode. 091 * 092 * @param value 093 * the value to round 094 * @param roundingMode 095 * the rounding mode indicating how the least significant returned decimal digit of the result is to be 096 * calculated 097 * @return the rounded value 098 * @see #getPrecision() 099 */ 100 public double round(double value, RoundingMode roundingMode) { 101 return round(value, roundingMode, scaleMetrics.getRoundingHalfEvenArithmetic(), ulp); 102 } 103 104 /** 105 * Returns a hash code for this <tt>DoubleRounder</tt> instance. 106 * 107 * @return a hash code value for this object. 108 */ 109 @Override 110 public int hashCode() { 111 return scaleMetrics.hashCode(); 112 } 113 114 /** 115 * Returns true if {@code obj} is a <tt>DoubleRounder</tt> with the same precision as {@code this} rounder instance. 116 * 117 * @param obj 118 * the reference object with which to compare 119 * @return true for a double rounder with the same precision as this instance 120 */ 121 @Override 122 public boolean equals(Object obj) { 123 if (obj == this) 124 return true; 125 if (obj == null) 126 return false; 127 if (obj instanceof DoubleRounder) { 128 return scaleMetrics.equals(((DoubleRounder) obj).scaleMetrics); 129 } 130 return false; 131 } 132 133 /** 134 * Returns a string consisting of the simple class name and the precision. 135 * 136 * @return a string like "DoubleRounder[precision=7]" 137 */ 138 @Override 139 public String toString() { 140 return "DoubleRounder[precision=" + getPrecision() + "]"; 141 } 142 143 /** 144 * Rounds the given double value to the specified decimal {@code precision} using {@link RoundingMode#HALF_UP 145 * HALF_UP} rounding. 146 * 147 * @param value 148 * the value to round 149 * @param precision 150 * the decimal precision to round to (aka decimal places) 151 * @return the rounded value 152 */ 153 public static final double round(double value, int precision) { 154 final ScaleMetrics sm = toScaleMetrics(precision); 155 final DecimalArithmetic halfEvenArith = sm.getRoundingHalfEvenArithmetic(); 156 return round(value, sm.getDefaultArithmetic(), halfEvenArith, halfEvenArith.toDouble(1)); 157 } 158 159 /** 160 * Rounds the given double value to the specified decimal {@code precision} using the specified rounding mode. 161 * 162 * @param value 163 * the value to round 164 * @param precision 165 * the decimal precision to round to (aka decimal places) 166 * @param roundingMode 167 * the rounding mode indicating how the least significant returned decimal digit of the result is to be 168 * calculated 169 * @return the rounded value 170 */ 171 public static final double round(double value, int precision, RoundingMode roundingMode) { 172 final ScaleMetrics sm = toScaleMetrics(precision); 173 final DecimalArithmetic halfEvenArith = sm.getRoundingHalfEvenArithmetic(); 174 return round(value, roundingMode, halfEvenArith, halfEvenArith.toDouble(1)); 175 } 176 177 private static final double round(double value, RoundingMode roundingMode, DecimalArithmetic halfEvenArith, double ulp) { 178 if (roundingMode == RoundingMode.UNNECESSARY) { 179 return checkRoundingUnnecessary(value, halfEvenArith, ulp); 180 } 181 return round(value, halfEvenArith.deriveArithmetic(roundingMode), halfEvenArith, ulp); 182 } 183 184 private static final double round(double value, DecimalArithmetic roundingArith, DecimalArithmetic halfEvenArith, double ulp) { 185 if (!isFinite(value) || 2 * ulp <= Math.ulp(value)) { 186 return value; 187 } 188 final long uDecimal = roundingArith.fromDouble(value); 189 return halfEvenArith.toDouble(uDecimal); 190 } 191 192 private static final double checkRoundingUnnecessary(double value, DecimalArithmetic halfEvenArith, double ulp) { 193 if (isFinite(value) && 2 * ulp > Math.ulp(value)) { 194 final long uDecimal = halfEvenArith.fromDouble(value); 195 if (halfEvenArith.toDouble(uDecimal) != value) { 196 throw new ArithmeticException( 197 "Rounding necessary for precision " + halfEvenArith.getScale() + ": " + value); 198 } 199 } 200 return value; 201 } 202 203 private static final ScaleMetrics toScaleMetrics(int precision) { 204 if (precision < Scales.MIN_SCALE | precision > Scales.MAX_SCALE) { 205 throw new IllegalArgumentException( 206 "Precision must be in [" + Scales.MIN_SCALE + "," + Scales.MAX_SCALE + "] but was " + precision); 207 } 208 return Scales.getScaleMetrics(precision); 209 } 210 211 /** 212 * Java-7 port of {@code Double#isFinite(double)}. 213 * <p> 214 * Returns {@code true} if the argument is a finite floating-point value; returns {@code false} otherwise (for NaN 215 * and infinity arguments). 216 * 217 * @param d 218 * the {@code double} value to be tested 219 * @return {@code true} if the argument is a finite floating-point value, {@code false} otherwise. 220 */ 221 private static boolean isFinite(double d) { 222 return Math.abs(d) <= Double.MAX_VALUE; 223 } 224}