public static Number numeric()

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);
  }