1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.value; |
7 | |
8 | import java.math.BigDecimal; |
9 | import java.sql.PreparedStatement; |
10 | import java.sql.SQLException; |
11 | |
12 | import org.h2.api.ErrorCode; |
13 | import org.h2.message.DbException; |
14 | import org.h2.util.MathUtils; |
15 | |
16 | /** |
17 | * Implementation of the DECIMAL data type. |
18 | */ |
19 | public class ValueDecimal extends Value { |
20 | |
21 | /** |
22 | * The value 'zero'. |
23 | */ |
24 | public static final Object ZERO = new ValueDecimal(BigDecimal.ZERO); |
25 | |
26 | /** |
27 | * The value 'one'. |
28 | */ |
29 | public static final Object ONE = new ValueDecimal(BigDecimal.ONE); |
30 | |
31 | /** |
32 | * The default precision for a decimal value. |
33 | */ |
34 | static final int DEFAULT_PRECISION = 65535; |
35 | |
36 | /** |
37 | * The default scale for a decimal value. |
38 | */ |
39 | static final int DEFAULT_SCALE = 32767; |
40 | |
41 | /** |
42 | * The default display size for a decimal value. |
43 | */ |
44 | static final int DEFAULT_DISPLAY_SIZE = 65535; |
45 | |
46 | private static final int DIVIDE_SCALE_ADD = 25; |
47 | |
48 | /** |
49 | * The maximum scale of a BigDecimal value. |
50 | */ |
51 | private static final int BIG_DECIMAL_SCALE_MAX = 100000; |
52 | |
53 | private final BigDecimal value; |
54 | private String valueString; |
55 | private int precision; |
56 | |
57 | private ValueDecimal(BigDecimal value) { |
58 | if (value == null) { |
59 | throw new IllegalArgumentException("null"); |
60 | } else if (!value.getClass().equals(BigDecimal.class)) { |
61 | throw DbException.get(ErrorCode.INVALID_CLASS_2, |
62 | BigDecimal.class.getName(), value.getClass().getName()); |
63 | } |
64 | this.value = value; |
65 | } |
66 | |
67 | @Override |
68 | public Value add(Value v) { |
69 | ValueDecimal dec = (ValueDecimal) v; |
70 | return ValueDecimal.get(value.add(dec.value)); |
71 | } |
72 | |
73 | @Override |
74 | public Value subtract(Value v) { |
75 | ValueDecimal dec = (ValueDecimal) v; |
76 | return ValueDecimal.get(value.subtract(dec.value)); |
77 | } |
78 | |
79 | @Override |
80 | public Value negate() { |
81 | return ValueDecimal.get(value.negate()); |
82 | } |
83 | |
84 | @Override |
85 | public Value multiply(Value v) { |
86 | ValueDecimal dec = (ValueDecimal) v; |
87 | return ValueDecimal.get(value.multiply(dec.value)); |
88 | } |
89 | |
90 | @Override |
91 | public Value divide(Value v) { |
92 | ValueDecimal dec = (ValueDecimal) v; |
93 | if (dec.value.signum() == 0) { |
94 | throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getSQL()); |
95 | } |
96 | BigDecimal bd = value.divide(dec.value, |
97 | value.scale() + DIVIDE_SCALE_ADD, |
98 | BigDecimal.ROUND_HALF_DOWN); |
99 | if (bd.signum() == 0) { |
100 | bd = BigDecimal.ZERO; |
101 | } else if (bd.scale() > 0) { |
102 | if (!bd.unscaledValue().testBit(0)) { |
103 | bd = bd.stripTrailingZeros(); |
104 | } |
105 | } |
106 | return ValueDecimal.get(bd); |
107 | } |
108 | |
109 | @Override |
110 | public ValueDecimal modulus(Value v) { |
111 | ValueDecimal dec = (ValueDecimal) v; |
112 | if (dec.value.signum() == 0) { |
113 | throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getSQL()); |
114 | } |
115 | BigDecimal bd = value.remainder(dec.value); |
116 | return ValueDecimal.get(bd); |
117 | } |
118 | |
119 | @Override |
120 | public String getSQL() { |
121 | return getString(); |
122 | } |
123 | |
124 | @Override |
125 | public int getType() { |
126 | return Value.DECIMAL; |
127 | } |
128 | |
129 | @Override |
130 | protected int compareSecure(Value o, CompareMode mode) { |
131 | ValueDecimal v = (ValueDecimal) o; |
132 | return value.compareTo(v.value); |
133 | } |
134 | |
135 | @Override |
136 | public int getSignum() { |
137 | return value.signum(); |
138 | } |
139 | |
140 | @Override |
141 | public BigDecimal getBigDecimal() { |
142 | return value; |
143 | } |
144 | |
145 | @Override |
146 | public String getString() { |
147 | if (valueString == null) { |
148 | String p = value.toPlainString(); |
149 | if (p.length() < 40) { |
150 | valueString = p; |
151 | } else { |
152 | valueString = value.toString(); |
153 | } |
154 | } |
155 | return valueString; |
156 | } |
157 | |
158 | @Override |
159 | public long getPrecision() { |
160 | if (precision == 0) { |
161 | precision = value.precision(); |
162 | } |
163 | return precision; |
164 | } |
165 | |
166 | @Override |
167 | public boolean checkPrecision(long prec) { |
168 | if (prec == DEFAULT_PRECISION) { |
169 | return true; |
170 | } |
171 | return getPrecision() <= prec; |
172 | } |
173 | |
174 | @Override |
175 | public int getScale() { |
176 | return value.scale(); |
177 | } |
178 | |
179 | @Override |
180 | public int hashCode() { |
181 | return value.hashCode(); |
182 | } |
183 | |
184 | @Override |
185 | public Object getObject() { |
186 | return value; |
187 | } |
188 | |
189 | @Override |
190 | public void set(PreparedStatement prep, int parameterIndex) |
191 | throws SQLException { |
192 | prep.setBigDecimal(parameterIndex, value); |
193 | } |
194 | |
195 | @Override |
196 | public Value convertScale(boolean onlyToSmallerScale, int targetScale) { |
197 | if (value.scale() == targetScale) { |
198 | return this; |
199 | } |
200 | if (onlyToSmallerScale || targetScale >= DEFAULT_SCALE) { |
201 | if (value.scale() < targetScale) { |
202 | return this; |
203 | } |
204 | } |
205 | BigDecimal bd = ValueDecimal.setScale(value, targetScale); |
206 | return ValueDecimal.get(bd); |
207 | } |
208 | |
209 | @Override |
210 | public Value convertPrecision(long precision, boolean force) { |
211 | if (getPrecision() <= precision) { |
212 | return this; |
213 | } |
214 | if (force) { |
215 | return get(BigDecimal.valueOf(value.doubleValue())); |
216 | } |
217 | throw DbException.get( |
218 | ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, |
219 | Long.toString(precision)); |
220 | } |
221 | |
222 | /** |
223 | * Get or create big decimal value for the given big decimal. |
224 | * |
225 | * @param dec the bit decimal |
226 | * @return the value |
227 | */ |
228 | public static ValueDecimal get(BigDecimal dec) { |
229 | if (BigDecimal.ZERO.equals(dec)) { |
230 | return (ValueDecimal) ZERO; |
231 | } else if (BigDecimal.ONE.equals(dec)) { |
232 | return (ValueDecimal) ONE; |
233 | } |
234 | return (ValueDecimal) Value.cache(new ValueDecimal(dec)); |
235 | } |
236 | |
237 | @Override |
238 | public int getDisplaySize() { |
239 | // add 2 characters for '-' and '.' |
240 | return MathUtils.convertLongToInt(getPrecision() + 2); |
241 | } |
242 | |
243 | @Override |
244 | public boolean equals(Object other) { |
245 | // Two BigDecimal objects are considered equal only if they are equal in |
246 | // value and scale (thus 2.0 is not equal to 2.00 when using equals; |
247 | // however -0.0 and 0.0 are). Can not use compareTo because 2.0 and 2.00 |
248 | // have different hash codes |
249 | return other instanceof ValueDecimal && |
250 | value.equals(((ValueDecimal) other).value); |
251 | } |
252 | |
253 | @Override |
254 | public int getMemory() { |
255 | return value.precision() + 120; |
256 | } |
257 | |
258 | /** |
259 | * Set the scale of a BigDecimal value. |
260 | * |
261 | * @param bd the BigDecimal value |
262 | * @param scale the new scale |
263 | * @return the scaled value |
264 | */ |
265 | public static BigDecimal setScale(BigDecimal bd, int scale) { |
266 | if (scale > BIG_DECIMAL_SCALE_MAX || scale < -BIG_DECIMAL_SCALE_MAX) { |
267 | throw DbException.getInvalidValueException("scale", scale); |
268 | } |
269 | return bd.setScale(scale, BigDecimal.ROUND_HALF_UP); |
270 | } |
271 | |
272 | } |