001/* 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2015-2023 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 org.decimal4j.api.DecimalArithmetic; 027import org.decimal4j.scale.ScaleMetrics; 028import org.decimal4j.truncate.DecimalRounding; 029 030/** 031 * Contains static methods to convert between different scales. 032 */ 033final class UnscaledConversion { 034 035 private static final int getScaleDiff(ScaleMetrics scaleMetrics, int scale) { 036 return getScaleDiff(scaleMetrics.getScale(), scale); 037 } 038 039 private static final int getScaleDiff(int targetScale, int sourceScale) { 040 final int diffScale = targetScale - sourceScale; 041 if (!Checked.isSubtractOverflow(targetScale, sourceScale, diffScale)) { 042 return diffScale; 043 } 044 throw new IllegalArgumentException("Cannot convert from scale " + sourceScale + " to " + targetScale 045 + " (scale difference is out of integer range)"); 046 } 047 048 /** 049 * Converts the given {@code unscaledValue} with the specified {@code scale} 050 * to a long value. The value is rounded DOWN if necessary. An exception is 051 * thrown if the conversion is not possible. 052 * 053 * @param arith 054 * arithmetic of the target value 055 * @param unscaledValue 056 * the unscaled value to convert 057 * @param scale 058 * the scale of {@code unscaledValue} 059 * @return a long value rounded down if necessary 060 * @throws IllegalArgumentException 061 * if the conversion cannot be performed due to overflow 062 */ 063 public static final long unscaledToLong(DecimalArithmetic arith, long unscaledValue, int scale) { 064 try { 065 return Pow10.divideByPowerOf10Checked(arith, unscaledValue, scale); 066 } catch (ArithmeticException e) { 067 throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale()); 068 } 069 } 070 071 /** 072 * Converts the given {@code unscaledValue} with the specified {@code scale} 073 * to a long value. The value is rounded using the specified 074 * {@code rounding} if necessary. An exception is thrown if the conversion 075 * is not possible. 076 * 077 * @param arith 078 * arithmetic of the target value 079 * @param rounding 080 * the rounding to apply if rounding is necessary 081 * @param unscaledValue 082 * the unscaled value to convert 083 * @param scale 084 * the scale of {@code unscaledValue} 085 * @return long value rounded with given rounding if necessary 086 * @throws IllegalArgumentException 087 * if the conversion cannot be performed due to overflow 088 * @throws ArithmeticException 089 * if rounding is necessary and {@code rounding==UNNECESSARY} 090 */ 091 public static final long unscaledToLong(DecimalArithmetic arith, DecimalRounding rounding, long unscaledValue, int scale) { 092 try { 093 return Pow10.divideByPowerOf10Checked(arith, rounding, unscaledValue, scale); 094 } catch (ArithmeticException e) { 095 throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale()); 096 } 097 } 098 099 /** 100 * Returns an unscaled value of the scale defined by {@code arith} given an 101 * {@code unscaledValue} with its {@code scale}. The value is rounded DOWN 102 * if necessary. An exception is thrown if the conversion is not possible. 103 * 104 * @param arith 105 * arithmetic defining the target scale 106 * @param unscaledValue 107 * the unscaled value to convert 108 * @param scale 109 * the scale of {@code unscaledValue} 110 * @return the unscaled value in the arithmetic's scale 111 * @throws IllegalArgumentException 112 * if the conversion cannot be performed due to overflow 113 */ 114 public static final long unscaledToUnscaled(DecimalArithmetic arith, long unscaledValue, int scale) { 115 final int scaleDiff = getScaleDiff(arith.getScaleMetrics(), scale); 116 try { 117 return Pow10.multiplyByPowerOf10Checked(arith, unscaledValue, scaleDiff); 118 } catch (ArithmeticException e) { 119 throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale()); 120 } 121 } 122 123 /** 124 * Returns an unscaled value of the scale defined by {@code arith} given an 125 * {@code unscaledValue} with its {@code scale}. The value is rounded using 126 * the specified {@code rounding} if necessary. An exception is thrown if 127 * the conversion is not possible. 128 * 129 * @param arith 130 * arithmetic defining the target scale 131 * @param rounding 132 * the rounding to apply if rounding is necessary 133 * @param unscaledValue 134 * the unscaled value to convert 135 * @param scale 136 * the scale of {@code unscaledValue} 137 * @return the unscaled value in the arithmetic's scale 138 * @throws IllegalArgumentException 139 * if the conversion cannot be performed due to overflow 140 * @throws ArithmeticException 141 * if rounding is necessary and {@code rounding==UNNECESSARY} 142 */ 143 public static final long unscaledToUnscaled(DecimalArithmetic arith, DecimalRounding rounding, long unscaledValue, int scale) { 144 final int scaleDiff = getScaleDiff(arith.getScaleMetrics(), scale); 145 try { 146 return Pow10.multiplyByPowerOf10Checked(arith, rounding, unscaledValue, scaleDiff); 147 } catch (ArithmeticException e) { 148 throw toIllegalArgumentExceptionOrRethrow(e, unscaledValue, scale, arith.getScale()); 149 } 150 } 151 152 /** 153 * Converts an unscaled value {@code uDecimal} having the scale specified by 154 * {@code arith} into another unscaled value of the provided 155 * {@code targetScale}. The value is rounded DOWN if necessary. An exception 156 * is thrown if the conversion is not possible. 157 * 158 * @param targetScale 159 * the scale of the result value 160 * @param arith 161 * arithmetic defining the source scale 162 * @param uDecimal 163 * the unscaled value to convert 164 * @return the unscaled value with {@code targetScale} 165 * @throws IllegalArgumentException 166 * if the conversion cannot be performed due to overflow 167 */ 168 public static final long unscaledToUnscaled(int targetScale, DecimalArithmetic arith, long uDecimal) { 169 final int scaleDiff = getScaleDiff(targetScale, arith.getScale()); 170 try { 171 return Pow10.multiplyByPowerOf10Checked(arith, uDecimal, scaleDiff); 172 } catch (ArithmeticException e) { 173 throw toIllegalArgumentExceptionOrRethrow(e, uDecimal, arith.getScale(), targetScale); 174 } 175 } 176 177 /** 178 * Converts an unscaled value {@code uDecimal} having the scale specified by 179 * {@code arith} into another unscaled value of the provided 180 * {@code targetScale}. The value is rounded using the specified 181 * {@code rounding} if necessary. An exception is thrown if the conversion 182 * is not possible. 183 * 184 * 185 * @param rounding 186 * the rounding to apply if rounding is necessary 187 * @param targetScale 188 * the scale of the result value 189 * @param arith 190 * arithmetic defining the source scale 191 * @param uDecimal 192 * the unscaled value to convert 193 * @return the unscaled value with {@code targetScale} 194 * @throws IllegalArgumentException 195 * if the conversion cannot be performed due to overflow 196 */ 197 public static final long unscaledToUnscaled(DecimalRounding rounding, int targetScale, DecimalArithmetic arith, long uDecimal) { 198 final int scaleDiff = getScaleDiff(targetScale, arith.getScale()); 199 try { 200 return Pow10.multiplyByPowerOf10Checked(arith, rounding, uDecimal, scaleDiff); 201 } catch (ArithmeticException e) { 202 throw toIllegalArgumentExceptionOrRethrow(e, uDecimal, arith.getScale(), targetScale); 203 } 204 } 205 206 private static final IllegalArgumentException toIllegalArgumentExceptionOrRethrow(ArithmeticException e, long unscaledValue, int sourceScale, int targetScale) { 207 Exceptions.rethrowIfRoundingNecessary(e); 208 if (targetScale > 0) { 209 return new IllegalArgumentException("Overflow: Cannot convert unscaled value " + unscaledValue 210 + " from scale " + sourceScale + " to scale " + targetScale, e); 211 } else { 212 return new IllegalArgumentException("Overflow: Cannot convert unscaled value " + unscaledValue 213 + " from scale " + sourceScale + " to long", e); 214 } 215 } 216 217 // no instances 218 private UnscaledConversion() { 219 } 220}