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.arithmetic; 025 026import java.math.BigDecimal; 027import java.math.BigInteger; 028import java.math.RoundingMode; 029 030import org.decimal4j.scale.ScaleMetrics; 031import org.decimal4j.scale.Scales; 032 033/** 034 * Contains methods to convert from and to {@link BigDecimal}. 035 */ 036final class BigDecimalConversion { 037 038 /** 039 * Converts the specified big decimal value to a long value applying the 040 * given rounding mode. An exception is thrown if the value exceeds the 041 * valid long range. 042 * 043 * @param roundingMode 044 * the rounding mode to apply if necessary 045 * @param value 046 * the big decimal value to convert 047 * @return <tt>round(value)</tt> 048 * @throws IllegalArgumentException 049 * if the value is outside of the valid long range 050 * @throws ArithmeticException 051 * if {@code roundingMode==UNNECESSARY} and rounding is 052 * necessary 053 */ 054 public static final long bigDecimalToLong(RoundingMode roundingMode, BigDecimal value) { 055 // TODO any chance to make this garbage free? 056 // Difficult as we cannot look inside the BigDecimal value 057 final BigInteger scaled = value// 058 .setScale(0, roundingMode)// 059 .toBigInteger(); 060 if (scaled.bitLength() <= 63) { 061 return scaled.longValue(); 062 } 063 throw new IllegalArgumentException("Overflow: cannot convert " + value + " to long"); 064 } 065 066 /** 067 * Converts the specified big decimal value to an unscaled decimal applying 068 * the given rounding mode if necessary. An exception is thrown if the value 069 * exceeds the valid Decimal range. 070 * 071 * @param scaleMetrics 072 * the scale metrics of the result value 073 * @param roundingMode 074 * the rounding mode to apply if necessary 075 * @param value 076 * the big decimal value to convert 077 * @return <tt>round(value)</tt> 078 * @throws IllegalArgumentException 079 * if the value is outside of the valid Decimal range 080 * @throws ArithmeticException 081 * if {@code roundingMode==UNNECESSARY} and rounding is 082 * necessary 083 */ 084 public static final long bigDecimalToUnscaled(ScaleMetrics scaleMetrics, RoundingMode roundingMode, BigDecimal value) { 085 // TODO any chance to make this garbage free? 086 // Difficult as we cannot look inside the BigDecimal value 087 final BigInteger scaled = value// 088 .multiply(scaleMetrics.getScaleFactorAsBigDecimal())// 089 .setScale(0, roundingMode)// 090 .toBigInteger(); 091 if (scaled.bitLength() <= 63) { 092 return scaled.longValue(); 093 } 094 throw new IllegalArgumentException( 095 "Overflow: cannot convert " + value + " to Decimal with scale " + scaleMetrics.getScale()); 096 } 097 098 /** 099 * Converts the given unscaled decimal value to a {@link BigDecimal} of the 100 * same scale as the given decimal value. 101 * 102 * @param scaleMetrics 103 * the scale metrics associated with the unscaled value 104 * @param uDecimal 105 * the unscaled decimal value to convert 106 * @return a big decimal with the scale from scale metrics 107 */ 108 public static final BigDecimal unscaledToBigDecimal(ScaleMetrics scaleMetrics, long uDecimal) { 109 return BigDecimal.valueOf(uDecimal, scaleMetrics.getScale()); 110 } 111 112 /** 113 * Converts the given unscaled decimal value to a {@link BigDecimal} of the 114 * specified {@code targetScale} rounding the value if necessary. 115 * 116 * @param scaleMetrics 117 * the scale metrics associated with the unscaled value 118 * @param roundingMode 119 * the rounding mode to use if rounding is necessary 120 * @param uDecimal 121 * the unscaled decimal value to convert 122 * @param targetScale 123 * the scale of the result value 124 * @return a big decimal with the specified {@code targetScale} 125 * @throws ArithmeticException 126 * if {@code roundingMode==UNNECESSARY} and rounding is 127 * necessary 128 */ 129 public static final BigDecimal unscaledToBigDecimal(ScaleMetrics scaleMetrics, RoundingMode roundingMode, long uDecimal, int targetScale) { 130 final int sourceScale = scaleMetrics.getScale(); 131 if (targetScale == sourceScale) { 132 return unscaledToBigDecimal(scaleMetrics, uDecimal); 133 } 134 if (targetScale < sourceScale) { 135 final int diff = sourceScale - targetScale; 136 if (diff <= 18) { 137 final ScaleMetrics diffMetrics = Scales.getScaleMetrics(diff); 138 final long rescaled = diffMetrics.getArithmetic(roundingMode).divideByPowerOf10(uDecimal, diff); 139 return BigDecimal.valueOf(rescaled, targetScale); 140 } 141 } else { 142 // does it fit in a long? 143 final int diff = targetScale - sourceScale; 144 if (diff <= 18) { 145 final ScaleMetrics diffMetrics = Scales.getScaleMetrics(diff); 146 if (diffMetrics.isValidIntegerValue(uDecimal)) { 147 final long rescaled = diffMetrics.multiplyByScaleFactor(uDecimal); 148 return BigDecimal.valueOf(rescaled, targetScale); 149 } 150 } 151 } 152 // let the big decimal deal with such large numbers then 153 return BigDecimal.valueOf(uDecimal, sourceScale).setScale(targetScale, roundingMode); 154 } 155 156 // no instances 157 private BigDecimalConversion() { 158 super(); 159 } 160}