in pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java [124:339]
public static Number numeric(byte [] bytes, int pos, int numBytes) {
if (numBytes < 8) {
throw new IllegalArgumentException("number of bytes should be at-least 8");
}
//number of 2-byte shorts representing 4 decimal digits
short len = ByteConverter.int2(bytes, pos);
//0 based number of 4 decimal digits (i.e. 2-byte shorts) before the decimal
//a value <= 0 indicates an absolute value < 1.
short weight = ByteConverter.int2(bytes, pos + 2);
//indicates positive, negative or NaN
short sign = ByteConverter.int2(bytes, pos + 4);
//number of digits after the decimal. This must be >= 0.
//a value of 0 indicates a whole number (integer).
short scale = ByteConverter.int2(bytes, pos + 6);
//An integer should be built from the len number of 2 byte shorts, treating each
//as 4 digits.
//The weight, if > 0, indicates how many of those 4 digit chunks should be to the
//"left" of the decimal. If the weight is 0, then all 4 digit chunks start immediately
//to the "right" of the decimal. If the weight is < 0, the absolute distance from 0
//indicates 4 leading "0" digits to the immediate "right" of the decimal, prior to the
//digits from "len".
//A weight which is positive, can be a number larger than what len defines. This means
//there are trailing 0s after the "len" integer and before the decimal.
//The scale indicates how many significant digits there are to the right of the decimal.
//A value of 0 indicates a whole number (integer).
//The combination of weight, len, and scale can result in either trimming digits provided
//by len (only to the right of the decimal) or adding significant 0 values to the right
//of len (on either side of the decimal).
if (numBytes != (len * SHORT_BYTES + 8)) {
throw new IllegalArgumentException("invalid length of bytes \"numeric\" value");
}
if (!(sign == NUMERIC_POS
|| sign == NUMERIC_NEG
|| sign == NUMERIC_NAN)) {
throw new IllegalArgumentException("invalid sign in \"numeric\" value");
}
if (sign == NUMERIC_NAN) {
return Double.NaN;
}
if ((scale & NUMERIC_DSCALE_MASK) != scale) {
throw new IllegalArgumentException("invalid scale in \"numeric\" value");
}
if (len == 0) {
return new BigDecimal(BigInteger.ZERO, scale);
}
int idx = pos + 8;
short d = ByteConverter.int2(bytes, idx);
//if the absolute value is (0, 1), then leading '0' values
//do not matter for the unscaledInt, but trailing 0s do
if (weight < 0) {
assert scale > 0;
int effectiveScale = scale;
++weight;
if (weight < 0) {
effectiveScale += (4 * weight);
}
int i = 1;
//typically there should not be leading 0 short values, as it is more
//efficient to represent that in the weight value
for ( ; i < len && d == 0; ++i) {
effectiveScale -= 4;
idx += 2;
d = ByteConverter.int2(bytes, idx);
}
BigInteger unscaledBI = null;
assert effectiveScale > 0;
if (effectiveScale >= 4) {
effectiveScale -= 4;
} else {
d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
effectiveScale = 0;
}
long unscaledInt = d;
for ( ; i < len; ++i) {
if (i == 4 && effectiveScale > 2) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
idx += 2;
d = ByteConverter.int2(bytes, idx);
if (effectiveScale >= 4) {
if (unscaledBI == null) {
unscaledInt *= 10000;
} else {
unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
}
effectiveScale -= 4;
} else {
if (unscaledBI == null) {
unscaledInt *= INT_TEN_POWERS[effectiveScale];
} else {
unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
}
d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
effectiveScale = 0;
}
if (unscaledBI == null) {
unscaledInt += d;
} else {
if (d != 0) {
unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
}
}
}
if (unscaledBI == null) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
if (effectiveScale > 0) {
unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
}
if (sign == NUMERIC_NEG) {
unscaledBI = unscaledBI.negate();
}
return new BigDecimal(unscaledBI, scale);
}
//if there is no scale, then shorts are the unscaled int
if (scale == 0) {
BigInteger unscaledBI = null;
long unscaledInt = d;
for (int i = 1; i < len; ++i) {
if (i == 4) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
idx += 2;
d = ByteConverter.int2(bytes, idx);
if (unscaledBI == null) {
unscaledInt *= 10000;
unscaledInt += d;
} else {
unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
if (d != 0) {
unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
}
}
}
if (unscaledBI == null) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
if (sign == NUMERIC_NEG) {
unscaledBI = unscaledBI.negate();
}
final int bigDecScale = (len - (weight + 1)) * 4;
//string representation always results in a BigDecimal with scale of 0
//the binary representation, where weight and len can infer trailing 0s, can result in a negative scale
//to produce a consistent BigDecimal, we return the equivalent object with scale set to 0
return bigDecScale == 0 ? new BigDecimal(unscaledBI) : new BigDecimal(unscaledBI, bigDecScale).setScale(0);
}
BigInteger unscaledBI = null;
long unscaledInt = d;
int effectiveWeight = weight;
int effectiveScale = scale;
for (int i = 1 ; i < len; ++i) {
if (i == 4) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
idx += 2;
d = ByteConverter.int2(bytes, idx);
if (effectiveWeight > 0) {
--effectiveWeight;
if (unscaledBI == null) {
unscaledInt *= 10000;
} else {
unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
}
} else if (effectiveScale >= 4) {
effectiveScale -= 4;
if (unscaledBI == null) {
unscaledInt *= 10000;
} else {
unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
}
} else {
if (unscaledBI == null) {
unscaledInt *= INT_TEN_POWERS[effectiveScale];
} else {
unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
}
d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
effectiveScale = 0;
}
if (unscaledBI == null) {
unscaledInt += d;
} else {
if (d != 0) {
unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
}
}
}
if (unscaledBI == null) {
unscaledBI = BigInteger.valueOf(unscaledInt);
}
if (effectiveScale > 0) {
unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
}
if (sign == NUMERIC_NEG) {
unscaledBI = unscaledBI.negate();
}
return new BigDecimal(unscaledBI, scale);
}