2 * Copyright (c) 2015 Pantheon Technologies s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.yangtools.yang.common;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
13 import com.google.common.annotations.Beta;
14 import com.google.common.annotations.VisibleForTesting;
15 import com.google.common.base.Strings;
16 import com.google.common.primitives.Longs;
17 import java.math.BigDecimal;
18 import org.opendaylight.yangtools.concepts.Immutable;
21 * Dedicated type for YANG's 'type decimal64' type. This class is similar to {@link BigDecimal}, but provides more
22 * efficient storage, as it has fixed precision.
24 * @author Robert Varga
27 public final class Decimal64 extends Number implements Comparable<Decimal64>, Immutable {
28 private static final long serialVersionUID = 1L;
30 private static final int MAX_FRACTION_DIGITS = 18;
32 private static final long[] SCALE = {
54 verify(SCALE.length == MAX_FRACTION_DIGITS);
57 private final byte scaleOffset;
58 private final long value;
61 Decimal64(final int fractionDigits, final long intPart, final long fracPart, final boolean negative) {
62 checkArgument(fractionDigits >= 1 && fractionDigits <= MAX_FRACTION_DIGITS);
63 this.scaleOffset = (byte) (fractionDigits - 1);
65 final long bits = intPart * SCALE[this.scaleOffset] + fracPart;
66 this.value = negative ? -bits : bits;
69 public static Decimal64 valueOf(final byte byteVal) {
70 return byteVal < 0 ? new Decimal64(1, -byteVal, 0, true) : new Decimal64(1, byteVal, 0, false);
73 public static Decimal64 valueOf(final short shortVal) {
74 return shortVal < 0 ? new Decimal64(1, -shortVal, 0, true) : new Decimal64(1, shortVal, 0, false);
77 public static Decimal64 valueOf(final int intVal) {
78 return intVal < 0 ? new Decimal64(1, - (long)intVal, 0, true) : new Decimal64(1, intVal, 0, false);
81 public static Decimal64 valueOf(final long longVal) {
82 // XXX: we should be able to do something smarter here
83 return valueOf(Long.toString(longVal));
86 public static Decimal64 valueOf(final double doubleVal) {
87 // XXX: we should be able to do something smarter here
88 return valueOf(Double.toString(doubleVal));
91 public static Decimal64 valueOf(final BigDecimal decimalVal) {
92 // XXX: we should be able to do something smarter here
93 return valueOf(decimalVal.toPlainString());
97 * Attempt to parse a String into a Decimal64. This method uses minimum fraction digits required to hold
100 * @param str String to parser
101 * @return A Decimal64 instance
102 * @throws NullPointerException if value is null.
103 * @throws NumberFormatException if the string does not contain a parsable decimal64.
105 public static Decimal64 valueOf(final String str) {
106 // https://tools.ietf.org/html/rfc6020#section-9.3.1
108 // A decimal64 value is lexically represented as an optional sign ("+"
109 // or "-"), followed by a sequence of decimal digits, optionally
110 // followed by a period ('.') as a decimal indicator and a sequence of
111 // decimal digits. If no sign is specified, "+" is assumed.
113 throw new NumberFormatException("Empty string is not a valid decimal64 representation");
116 // Deal with optional sign
117 final boolean negative;
119 switch (str.charAt(0)) {
133 // Sanity check length
134 if (idx == str.length()) {
135 throw new NumberFormatException("Missing digits after sign");
138 // Character limit, used for caching and cutting trailing zeroes
139 int limit = str.length() - 1;
141 // Skip any leading zeroes, but leave at least one
142 for (; idx < limit && str.charAt(idx) == '0'; idx++) {
143 final char ch = str.charAt(idx + 1);
144 if (ch < '0' || ch > '9') {
149 // Integer part and its length
153 for (; idx <= limit; idx++, intLen++) {
154 final char ch = str.charAt(idx);
156 // Fractions are next
159 if (intLen == MAX_FRACTION_DIGITS) {
160 throw new NumberFormatException("Integer part is longer than " + MAX_FRACTION_DIGITS + " digits");
163 intPart = 10 * intPart + toInt(ch, idx);
167 // No fraction digits, we are done
168 return new Decimal64((byte)1, intPart, 0, negative);
171 // Bump index to skip over period and check the remainder
174 throw new NumberFormatException("Value '" + str + "' is missing fraction digits");
177 // Trim trailing zeroes, if any
178 while (idx < limit && str.charAt(limit) == '0') {
182 final int fracLimit = MAX_FRACTION_DIGITS - intLen;
185 for (; idx <= limit; idx++, fracLen++) {
186 final char ch = str.charAt(idx);
187 if (fracLen == fracLimit) {
188 throw new NumberFormatException("Fraction part longer than " + fracLimit + " digits");
191 fracPart = 10 * fracPart + toInt(ch, idx);
194 return new Decimal64(fracLen, intPart, fracPart, negative);
197 public BigDecimal decimalValue() {
198 return BigDecimal.valueOf(value, scaleOffset + 1);
202 public int intValue() {
203 return (int) intPart();
207 public long longValue() {
212 public float floatValue() {
213 return (float) doubleValue();
217 public double doubleValue() {
218 return 1.0 * value / SCALE[scaleOffset];
222 * Converts this {@code BigDecimal} to a {@code byte}, checking for lost information. If this {@code Decimal64} has
223 * a nonzero fractional part or is out of the possible range for a {@code byte} result then
224 * an {@code ArithmeticException} is thrown.
226 * @return this {@code Decimal64} converted to a {@code byte}.
227 * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code byte}.
229 public byte byteValueExact() {
230 final long val = longValueExact();
231 final byte ret = (byte) val;
233 throw new ArithmeticException("Value " + val + " is outside of byte range");
239 * Converts this {@code BigDecimal} to a {@code short}, checking for lost information. If this {@code Decimal64} has
240 * a nonzero fractional part or is out of the possible range for a {@code short} result then
241 * an {@code ArithmeticException} is thrown.
243 * @return this {@code Decimal64} converted to a {@code short}.
244 * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code short}.
246 public short shortValueExact() {
247 final long val = longValueExact();
248 final short ret = (short) val;
250 throw new ArithmeticException("Value " + val + " is outside of short range");
256 * Converts this {@code BigDecimal} to an {@code int}, checking for lost information. If this {@code Decimal64} has
257 * a nonzero fractional part or is out of the possible range for an {@code int} result then
258 * an {@code ArithmeticException} is thrown.
260 * @return this {@code Decimal64} converted to an {@code int}.
261 * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in an {@code int}.
263 public int intValueExact() {
264 final long val = longValueExact();
265 final int ret = (int) val;
267 throw new ArithmeticException("Value " + val + " is outside of integer range");
273 * Converts this {@code BigDecimal} to a {@code long}, checking for lost information. If this {@code Decimal64} has
274 * a nonzero fractional part then an {@code ArithmeticException} is thrown.
276 * @return this {@code Decimal64} converted to a {@code long}.
277 * @throws ArithmeticException if {@code this} has a nonzero fractional part.
279 public long longValueExact() {
280 if (fracPart() != 0) {
281 throw new ArithmeticException("Conversion of " + this + " would lose fraction");
287 @SuppressWarnings("checkstyle:parameterName")
288 public int compareTo(final Decimal64 o) {
292 if (scaleOffset == o.scaleOffset) {
293 return Long.compare(value, o.value);
296 // XXX: we could do something smarter here
297 return Double.compare(doubleValue(), o.doubleValue());
301 public int hashCode() {
302 // We need to normalize the results in order to be consistent with equals()
303 return Longs.hashCode(intPart()) * 31 + Long.hashCode(fracPart());
307 public boolean equals(final Object obj) {
311 if (!(obj instanceof Decimal64)) {
314 final Decimal64 other = (Decimal64) obj;
315 if (scaleOffset == other.scaleOffset) {
316 return value == other.value;
319 // We need to normalize both
320 return intPart() == other.intPart() && fracPart() == fracPart();
324 public String toString() {
325 // https://tools.ietf.org/html/rfc6020#section-9.3.2
327 // The canonical form of a positive decimal64 does not include the sign
328 // "+". The decimal point is required. Leading and trailing zeros are
329 // prohibited, subject to the rule that there MUST be at least one digit
330 // before and after the decimal point. The value zero is represented as
332 final StringBuilder sb = new StringBuilder(21).append(intPart()).append('.');
333 final long fracPart = fracPart();
335 // We may need to zero-pad the fraction part
336 sb.append(Strings.padStart(Long.toString(fracPart), scaleOffset + 1, '0'));
341 return sb.toString();
344 private long intPart() {
345 return value / SCALE[scaleOffset];
348 private long fracPart() {
349 return Math.abs(value % SCALE[scaleOffset]);
352 private static int toInt(final char ch, final int index) {
353 if (ch < '0' || ch > '9') {
354 throw new NumberFormatException("Illegal character at offset " + index);