* If the high bits of the first word of a NumericChoice (n_header, or
* n_short.n_header, or n_long.n_sign_dscale) are NUMERIC_SHORT, then the
* numeric follows the NumericShort format; if they are NUMERIC_POS or
- * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_NAN,
- * it is a NaN. We currently always store a NaN using just two bytes (i.e.
- * only n_header), but previous releases used only the NumericLong format,
- * so we might find 4-byte NaNs on disk if a database has been migrated using
- * pg_upgrade. In either case, when the high bits indicate a NaN, the
- * remaining bits are never examined. Currently, we always initialize these
- * to zero, but it might be possible to use them for some other purpose in
- * the future.
+ * NUMERIC_NEG, it follows the NumericLong format. If they are NUMERIC_SPECIAL,
+ * the value is a NaN or Infinity. We currently always store SPECIAL values
+ * using just two bytes (i.e. only n_header), but previous releases used only
+ * the NumericLong format, so we might find 4-byte NaNs (though not infinities)
+ * on disk if a database has been migrated using pg_upgrade. In either case,
+ * the low-order bits of a special value's header are reserved and currently
+ * should always be set to zero.
*
* In the NumericShort format, the remaining 14 bits of the header word
* (n_short.n_header) are allocated as follows: 1 for sign (positive or
#define NUMERIC_POS 0x0000
#define NUMERIC_NEG 0x4000
#define NUMERIC_SHORT 0x8000
-#define NUMERIC_NAN 0xC000
+#define NUMERIC_SPECIAL 0xC000
#define NUMERIC_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_SIGN_MASK)
-#define NUMERIC_IS_NAN(n) (NUMERIC_FLAGBITS(n) == NUMERIC_NAN)
#define NUMERIC_IS_SHORT(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SHORT)
+#define NUMERIC_IS_SPECIAL(n) (NUMERIC_FLAGBITS(n) == NUMERIC_SPECIAL)
#define NUMERIC_HDRSZ (VARHDRSZ + sizeof(uint16) + sizeof(int16))
#define NUMERIC_HDRSZ_SHORT (VARHDRSZ + sizeof(uint16))
/*
- * If the flag bits are NUMERIC_SHORT or NUMERIC_NAN, we want the short header;
- * otherwise, we want the long one. Instead of testing against each value, we
- * can just look at the high bit, for a slight efficiency gain.
+ * If the flag bits are NUMERIC_SHORT or NUMERIC_SPECIAL, we want the short
+ * header; otherwise, we want the long one. Instead of testing against each
+ * value, we can just look at the high bit, for a slight efficiency gain.
*/
#define NUMERIC_HEADER_IS_SHORT(n) (((n)->choice.n_header & 0x8000) != 0)
#define NUMERIC_HEADER_SIZE(n) \
(VARHDRSZ + sizeof(uint16) + \
(NUMERIC_HEADER_IS_SHORT(n) ? 0 : sizeof(int16)))
+/*
+ * Definitions for special values (NaN, positive infinity, negative infinity).
+ *
+ * The two bits after the NUMERIC_SPECIAL bits are 00 for NaN, 01 for positive
+ * infinity, 11 for negative infinity. (This makes the sign bit match where
+ * it is in a short-format value, though we make no use of that at present.)
+ * We could mask off the remaining bits before testing the active bits, but
+ * currently those bits must be zeroes, so masking would just add cycles.
+ */
+#define NUMERIC_EXT_SIGN_MASK 0xF000 /* high bits plus NaN/Inf flag bits */
+#define NUMERIC_NAN 0xC000
+#define NUMERIC_PINF 0xD000
+#define NUMERIC_NINF 0xF000
+#define NUMERIC_INF_SIGN_MASK 0x2000
+
+#define NUMERIC_EXT_FLAGBITS(n) ((n)->choice.n_header & NUMERIC_EXT_SIGN_MASK)
+#define NUMERIC_IS_NAN(n) ((n)->choice.n_header == NUMERIC_NAN)
+#define NUMERIC_IS_PINF(n) ((n)->choice.n_header == NUMERIC_PINF)
+#define NUMERIC_IS_NINF(n) ((n)->choice.n_header == NUMERIC_NINF)
+#define NUMERIC_IS_INF(n) \
+ (((n)->choice.n_header & ~NUMERIC_INF_SIGN_MASK) == NUMERIC_PINF)
+
/*
* Short format definitions.
*/
#define NUMERIC_SHORT_WEIGHT_MIN (-(NUMERIC_SHORT_WEIGHT_MASK+1))
/*
- * Extract sign, display scale, weight.
+ * Extract sign, display scale, weight. These macros extract field values
+ * suitable for the NumericVar format from the Numeric (on-disk) format.
+ *
+ * Note that we don't trouble to ensure that dscale and weight read as zero
+ * for an infinity; however, that doesn't matter since we never convert
+ * "special" numerics to NumericVar form. Only the constants defined below
+ * (const_nan, etc) ever represent a non-finite value as a NumericVar.
*/
#define NUMERIC_DSCALE_MASK 0x3FFF
#define NUMERIC_SIGN(n) \
(NUMERIC_IS_SHORT(n) ? \
(((n)->choice.n_short.n_header & NUMERIC_SHORT_SIGN_MASK) ? \
- NUMERIC_NEG : NUMERIC_POS) : NUMERIC_FLAGBITS(n))
+ NUMERIC_NEG : NUMERIC_POS) : \
+ (NUMERIC_IS_SPECIAL(n) ? \
+ NUMERIC_EXT_FLAGBITS(n) : NUMERIC_FLAGBITS(n)))
#define NUMERIC_DSCALE(n) (NUMERIC_HEADER_IS_SHORT((n)) ? \
((n)->choice.n_short.n_header & NUMERIC_SHORT_DSCALE_MASK) \
>> NUMERIC_SHORT_DSCALE_SHIFT \
* complex.
*
* The value represented by a NumericVar is determined by the sign, weight,
- * ndigits, and digits[] array.
+ * ndigits, and digits[] array. If it is a "special" value (NaN or Inf)
+ * then only the sign field matters; ndigits should be zero, and the weight
+ * and dscale fields are ignored.
*
* Note: the first digit of a NumericVar's value is assumed to be multiplied
* by NBASE ** weight. Another way to say it is that there are weight+1
{
int ndigits; /* # of digits in digits[] - can be 0! */
int weight; /* weight of first digit */
- int sign; /* NUMERIC_POS, NUMERIC_NEG, or NUMERIC_NAN */
+ int sign; /* NUMERIC_POS, _NEG, _NAN, _PINF, or _NINF */
int dscale; /* display scale */
NumericDigit *buf; /* start of palloc'd space for digits[] */
NumericDigit *digits; /* base-NBASE digits */
* representations for numeric values in order to avoid depending on
* USE_FLOAT8_BYVAL. The type of abbreviation we use is based only on
* the size of a datum, not the argument-passing convention for float8.
+ *
+ * The range of abbreviations for finite values is from +PG_INT64/32_MAX
+ * to -PG_INT64/32_MAX. NaN has the abbreviation PG_INT64/32_MIN, and we
+ * define the sort ordering to make that work out properly (see further
+ * comments below). PINF and NINF share the abbreviations of the largest
+ * and smallest finite abbreviation classes.
*/
#define NUMERIC_ABBREV_BITS (SIZEOF_DATUM * BITS_PER_BYTE)
#if SIZEOF_DATUM == 8
#define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int64) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT64_MIN)
+#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT64_MAX)
+#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT64_MAX)
#else
#define NumericAbbrevGetDatum(X) ((Datum) (X))
#define DatumGetNumericAbbrev(X) ((int32) (X))
#define NUMERIC_ABBREV_NAN NumericAbbrevGetDatum(PG_INT32_MIN)
+#define NUMERIC_ABBREV_PINF NumericAbbrevGetDatum(-PG_INT32_MAX)
+#define NUMERIC_ABBREV_NINF NumericAbbrevGetDatum(PG_INT32_MAX)
#endif
static const NumericVar const_one =
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_one_data};
+static const NumericVar const_minus_one =
+{1, 0, NUMERIC_NEG, 0, NULL, (NumericDigit *) const_one_data};
+
static const NumericDigit const_two_data[1] = {2};
static const NumericVar const_two =
{1, 0, NUMERIC_POS, 0, NULL, (NumericDigit *) const_two_data};
static const NumericVar const_nan =
{0, 0, NUMERIC_NAN, 0, NULL, NULL};
+static const NumericVar const_pinf =
+{0, 0, NUMERIC_PINF, 0, NULL, NULL};
+
+static const NumericVar const_ninf =
+{0, 0, NUMERIC_NINF, 0, NULL, NULL};
+
#if DEC_DIGITS == 4
static const int round_powers[4] = {0, 1000, 100, 10};
#endif
static char *get_str_from_var(const NumericVar *var);
static char *get_str_from_var_sci(const NumericVar *var, int rscale);
+static Numeric duplicate_numeric(Numeric num);
static Numeric make_result(const NumericVar *var);
static Numeric make_result_opt_error(const NumericVar *var, bool *error);
static void apply_typmod(NumericVar *var, int32 typmod);
+static void apply_typmod_special(Numeric num, int32 typmod);
static bool numericvar_to_int32(const NumericVar *var, int32 *result);
static bool numericvar_to_int64(const NumericVar *var, int64 *result);
static bool numericvar_to_int128(const NumericVar *var, int128 *result);
static void int128_to_numericvar(int128 val, NumericVar *var);
#endif
-static double numeric_to_double_no_overflow(Numeric num);
static double numericvar_to_double_no_overflow(const NumericVar *var);
static Datum numeric_abbrev_convert(Datum original_datum, SortSupport ssup);
}
/*
- * Check for NaN
+ * Check for NaN and infinities. We recognize the same strings allowed by
+ * float8in().
*/
if (pg_strncasecmp(cp, "NaN", 3) == 0)
{
res = make_result(&const_nan);
-
- /* Should be nothing left but spaces */
cp += 3;
- while (*cp)
- {
- if (!isspace((unsigned char) *cp))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "numeric", str)));
- cp++;
- }
+ }
+ else if (pg_strncasecmp(cp, "Infinity", 8) == 0)
+ {
+ res = make_result(&const_pinf);
+ cp += 8;
+ }
+ else if (pg_strncasecmp(cp, "+Infinity", 9) == 0)
+ {
+ res = make_result(&const_pinf);
+ cp += 9;
+ }
+ else if (pg_strncasecmp(cp, "-Infinity", 9) == 0)
+ {
+ res = make_result(&const_ninf);
+ cp += 9;
+ }
+ else if (pg_strncasecmp(cp, "inf", 3) == 0)
+ {
+ res = make_result(&const_pinf);
+ cp += 3;
+ }
+ else if (pg_strncasecmp(cp, "+inf", 4) == 0)
+ {
+ res = make_result(&const_pinf);
+ cp += 4;
+ }
+ else if (pg_strncasecmp(cp, "-inf", 4) == 0)
+ {
+ res = make_result(&const_ninf);
+ cp += 4;
}
else
{
* We duplicate a few lines of code here because we would like to
* throw any trailing-junk syntax error before any semantic error
* resulting from apply_typmod. We can't easily fold the two cases
- * together because we mustn't apply apply_typmod to a NaN.
+ * together because we mustn't apply apply_typmod to a NaN/Inf.
*/
while (*cp)
{
res = make_result(&value);
free_var(&value);
+
+ PG_RETURN_NUMERIC(res);
}
+ /* Should be nothing left but spaces */
+ while (*cp)
+ {
+ if (!isspace((unsigned char) *cp))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("invalid input syntax for type %s: \"%s\"",
+ "numeric", str)));
+ cp++;
+ }
+
+ /* As above, throw any typmod error after finishing syntax check */
+ apply_typmod_special(res, typmod);
+
PG_RETURN_NUMERIC(res);
}
char *str;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_CSTRING(pstrdup("NaN"));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ PG_RETURN_CSTRING(pstrdup("Infinity"));
+ else if (NUMERIC_IS_NINF(num))
+ PG_RETURN_CSTRING(pstrdup("-Infinity"));
+ else
+ PG_RETURN_CSTRING(pstrdup("NaN"));
+ }
/*
* Get the number in the variable format.
return NUMERIC_IS_NAN(num);
}
+/*
+ * numeric_is_inf() -
+ *
+ * Is Numeric value an infinity?
+ */
+bool
+numeric_is_inf(Numeric num)
+{
+ return NUMERIC_IS_INF(num);
+}
+
+/*
+ * numeric_is_integral() -
+ *
+ * Is Numeric value integral?
+ */
+static bool
+numeric_is_integral(Numeric num)
+{
+ NumericVar arg;
+
+ /* Reject NaN, but infinities are considered integral */
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_NAN(num))
+ return false;
+ return true;
+ }
+
+ /* Integral if there are no digits to the right of the decimal point */
+ init_var_from_num(num, &arg);
+
+ return (arg.ndigits == 0 || arg.ndigits <= arg.weight + 1);
+}
+
/*
* numeric_maximum_size() -
*
char *str;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- return pstrdup("NaN");
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ return pstrdup("Infinity");
+ else if (NUMERIC_IS_NINF(num))
+ return pstrdup("-Infinity");
+ else
+ return pstrdup("NaN");
+ }
init_var_from_num(num, &x);
int last;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- return pstrdup("NaN");
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ return pstrdup("Infinity");
+ else if (NUMERIC_IS_NINF(num))
+ return pstrdup("-Infinity");
+ else
+ return pstrdup("NaN");
+ }
init_var_from_num(num, &x);
value.sign = (uint16) pq_getmsgint(buf, sizeof(uint16));
if (!(value.sign == NUMERIC_POS ||
value.sign == NUMERIC_NEG ||
- value.sign == NUMERIC_NAN))
+ value.sign == NUMERIC_NAN ||
+ value.sign == NUMERIC_PINF ||
+ value.sign == NUMERIC_NINF))
ereport(ERROR,
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
errmsg("invalid sign in external \"numeric\" value")));
* If the given dscale would hide any digits, truncate those digits away.
* We could alternatively throw an error, but that would take a bunch of
* extra code (about as much as trunc_var involves), and it might cause
- * client compatibility issues.
+ * client compatibility issues. Be careful not to apply trunc_var to
+ * special values, as it could do the wrong thing; we don't need it
+ * anyway, since make_result will ignore all but the sign field.
+ *
+ * After doing that, be sure to check the typmod restriction.
*/
- trunc_var(&value, value.dscale);
+ if (value.sign == NUMERIC_POS ||
+ value.sign == NUMERIC_NEG)
+ {
+ trunc_var(&value, value.dscale);
- apply_typmod(&value, typmod);
+ apply_typmod(&value, typmod);
+
+ res = make_result(&value);
+ }
+ else
+ {
+ /* apply_typmod_special wants us to make the Numeric first */
+ res = make_result(&value);
+
+ apply_typmod_special(res, typmod);
+ }
- res = make_result(&value);
free_var(&value);
PG_RETURN_NUMERIC(res);
NumericVar var;
/*
- * Handle NaN
+ * Handle NaN and infinities: if apply_typmod_special doesn't complain,
+ * just return a copy of the input.
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ apply_typmod_special(num, typmod);
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
+ }
/*
* If the value isn't a valid type modifier, simply return a copy of the
* input value
*/
if (typmod < (int32) (VARHDRSZ))
- {
- new = (Numeric) palloc(VARSIZE(num));
- memcpy(new, num, VARSIZE(num));
- PG_RETURN_NUMERIC(new);
- }
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
* Get the precision and scale out of the typmod value
&& (NUMERIC_CAN_BE_SHORT(scale, NUMERIC_WEIGHT(num))
|| !NUMERIC_IS_SHORT(num)))
{
- new = (Numeric) palloc(VARSIZE(num));
- memcpy(new, num, VARSIZE(num));
+ new = duplicate_numeric(num);
if (NUMERIC_IS_SHORT(num))
new->choice.n_short.n_header =
(num->choice.n_short.n_header & ~NUMERIC_SHORT_DSCALE_MASK)
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
- /*
- * Handle NaN
- */
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
-
/*
* Do it the easy way directly on the packed format
*/
- res = (Numeric) palloc(VARSIZE(num));
- memcpy(res, num, VARSIZE(num));
+ res = duplicate_numeric(num);
if (NUMERIC_IS_SHORT(num))
res->choice.n_short.n_header =
num->choice.n_short.n_header & ~NUMERIC_SHORT_SIGN_MASK;
+ else if (NUMERIC_IS_SPECIAL(num))
+ {
+ /* This changes -Inf to Inf, and doesn't affect NaN */
+ res->choice.n_short.n_header =
+ num->choice.n_short.n_header & ~NUMERIC_INF_SIGN_MASK;
+ }
else
res->choice.n_long.n_sign_dscale = NUMERIC_POS | NUMERIC_DSCALE(num);
Numeric num = PG_GETARG_NUMERIC(0);
Numeric res;
- /*
- * Handle NaN
- */
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
-
/*
* Do it the easy way directly on the packed format
*/
- res = (Numeric) palloc(VARSIZE(num));
- memcpy(res, num, VARSIZE(num));
+ res = duplicate_numeric(num);
+
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ /* Flip the sign, if it's Inf or -Inf */
+ if (!NUMERIC_IS_NAN(num))
+ res->choice.n_short.n_header =
+ num->choice.n_short.n_header ^ NUMERIC_INF_SIGN_MASK;
+ }
/*
* The packed format is known to be totally zero digit trimmed always. So
- * we can identify a ZERO by the fact that there are no digits at all. Do
- * nothing to a zero.
+ * once we've eliminated specials, we can identify a zero by the fact that
+ * there are no digits at all. Do nothing to a zero.
*/
- if (NUMERIC_NDIGITS(num) != 0)
+ else if (NUMERIC_NDIGITS(num) != 0)
{
/* Else, flip the sign */
if (NUMERIC_IS_SHORT(num))
numeric_uplus(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
- Numeric res;
- res = (Numeric) palloc(VARSIZE(num));
- memcpy(res, num, VARSIZE(num));
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
+}
- PG_RETURN_NUMERIC(res);
+
+/*
+ * numeric_sign_internal() -
+ *
+ * Returns -1 if the argument is less than 0, 0 if the argument is equal
+ * to 0, and 1 if the argument is greater than zero. Caller must have
+ * taken care of the NaN case, but we can handle infinities here.
+ */
+static int
+numeric_sign_internal(Numeric num)
+{
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ Assert(!NUMERIC_IS_NAN(num));
+ /* Must be Inf or -Inf */
+ if (NUMERIC_IS_PINF(num))
+ return 1;
+ else
+ return -1;
+ }
+
+ /*
+ * The packed format is known to be totally zero digit trimmed always. So
+ * once we've eliminated specials, we can identify a zero by the fact that
+ * there are no digits at all.
+ */
+ else if (NUMERIC_NDIGITS(num) == 0)
+ return 0;
+ else if (NUMERIC_SIGN(num) == NUMERIC_NEG)
+ return -1;
+ else
+ return 1;
}
/*
numeric_sign(PG_FUNCTION_ARGS)
{
Numeric num = PG_GETARG_NUMERIC(0);
- Numeric res;
- NumericVar result;
/*
- * Handle NaN
+ * Handle NaN (infinities can be handled normally)
*/
if (NUMERIC_IS_NAN(num))
PG_RETURN_NUMERIC(make_result(&const_nan));
- init_var(&result);
-
- /*
- * The packed format is known to be totally zero digit trimmed always. So
- * we can identify a ZERO by the fact that there are no digits at all.
- */
- if (NUMERIC_NDIGITS(num) == 0)
- set_var_from_var(&const_zero, &result);
- else
+ switch (numeric_sign_internal(num))
{
- /*
- * And if there are some, we return a copy of ONE with the sign of our
- * argument
- */
- set_var_from_var(&const_one, &result);
- result.sign = NUMERIC_SIGN(num);
+ case 0:
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ case 1:
+ PG_RETURN_NUMERIC(make_result(&const_one));
+ case -1:
+ PG_RETURN_NUMERIC(make_result(&const_minus_one));
}
- res = make_result(&result);
- free_var(&result);
-
- PG_RETURN_NUMERIC(res);
+ Assert(false);
+ return (Datum) 0;
}
NumericVar arg;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
* Limit the scale value to avoid possible overflow in calculations
NumericVar arg;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
* Limit the scale value to avoid possible overflow in calculations
Numeric res;
NumericVar result;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ /*
+ * Handle NaN and infinities
+ */
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result);
ceil_var(&result, &result);
Numeric res;
NumericVar result;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ /*
+ * Handle NaN and infinities
+ */
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result);
floor_var(&result, &result);
Numeric stop_num = PG_GETARG_NUMERIC(1);
NumericVar steploc = const_one;
- /* handle NaN in start and stop values */
- if (NUMERIC_IS_NAN(start_num))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("start value cannot be NaN")));
-
- if (NUMERIC_IS_NAN(stop_num))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("stop value cannot be NaN")));
+ /* Reject NaN and infinities in start and stop values */
+ if (NUMERIC_IS_SPECIAL(start_num))
+ {
+ if (NUMERIC_IS_NAN(start_num))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("start value cannot be NaN")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("start value cannot be infinity")));
+ }
+ if (NUMERIC_IS_SPECIAL(stop_num))
+ {
+ if (NUMERIC_IS_NAN(stop_num))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stop value cannot be NaN")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stop value cannot be infinity")));
+ }
/* see if we were given an explicit step size */
if (PG_NARGS() == 3)
{
Numeric step_num = PG_GETARG_NUMERIC(2);
- if (NUMERIC_IS_NAN(step_num))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("step size cannot be NaN")));
+ if (NUMERIC_IS_SPECIAL(step_num))
+ {
+ if (NUMERIC_IS_NAN(step_num))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be NaN")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinity")));
+ }
init_var_from_num(step_num, &steploc);
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
errmsg("count must be greater than zero")));
- if (NUMERIC_IS_NAN(operand) ||
- NUMERIC_IS_NAN(bound1) ||
- NUMERIC_IS_NAN(bound2))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
- errmsg("operand, lower bound, and upper bound cannot be NaN")));
+ if (NUMERIC_IS_SPECIAL(operand) ||
+ NUMERIC_IS_SPECIAL(bound1) ||
+ NUMERIC_IS_SPECIAL(bound2))
+ {
+ if (NUMERIC_IS_NAN(operand) ||
+ NUMERIC_IS_NAN(bound1) ||
+ NUMERIC_IS_NAN(bound2))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
+ errmsg("operand, lower bound, and upper bound cannot be NaN")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION),
+ errmsg("operand, lower bound, and upper bound cannot be infinity")));
+ }
init_var(&result_var);
init_var(&count_var);
else
value = (Numeric) original_varatt;
- if (NUMERIC_IS_NAN(value))
+ if (NUMERIC_IS_SPECIAL(value))
{
- result = NUMERIC_ABBREV_NAN;
+ if (NUMERIC_IS_PINF(value))
+ result = NUMERIC_ABBREV_PINF;
+ else if (NUMERIC_IS_NINF(value))
+ result = NUMERIC_ABBREV_NINF;
+ else
+ result = NUMERIC_ABBREV_NAN;
}
else
{
{
/*
* NOTE WELL: this is intentionally backwards, because the abbreviation is
- * negated relative to the original value, to handle NaN.
+ * negated relative to the original value, to handle NaN/infinity cases.
*/
if (DatumGetNumericAbbrev(x) < DatumGetNumericAbbrev(y))
return 1;
int result;
/*
- * We consider all NANs to be equal and larger than any non-NAN. This is
- * somewhat arbitrary; the important thing is to have a consistent sort
- * order.
+ * We consider all NANs to be equal and larger than any non-NAN (including
+ * Infinity). This is somewhat arbitrary; the important thing is to have
+ * a consistent sort order.
*/
- if (NUMERIC_IS_NAN(num1))
+ if (NUMERIC_IS_SPECIAL(num1))
{
- if (NUMERIC_IS_NAN(num2))
- result = 0; /* NAN = NAN */
- else
- result = 1; /* NAN > non-NAN */
+ if (NUMERIC_IS_NAN(num1))
+ {
+ if (NUMERIC_IS_NAN(num2))
+ result = 0; /* NAN = NAN */
+ else
+ result = 1; /* NAN > non-NAN */
+ }
+ else if (NUMERIC_IS_PINF(num1))
+ {
+ if (NUMERIC_IS_NAN(num2))
+ result = -1; /* PINF < NAN */
+ else if (NUMERIC_IS_PINF(num2))
+ result = 0; /* PINF = PINF */
+ else
+ result = 1; /* PINF > anything else */
+ }
+ else /* num1 must be NINF */
+ {
+ if (NUMERIC_IS_NINF(num2))
+ result = 0; /* NINF = NINF */
+ else
+ result = -1; /* NINF < anything else */
+ }
}
- else if (NUMERIC_IS_NAN(num2))
+ else if (NUMERIC_IS_SPECIAL(num2))
{
- result = -1; /* non-NAN < NAN */
+ if (NUMERIC_IS_NINF(num2))
+ result = 1; /* normal > NINF */
+ else
+ result = -1; /* normal < NAN or PINF */
}
else
{
bool result;
/*
- * Reject negative or NaN offset. Negative is per spec, and NaN is
- * because appropriate semantics for that seem non-obvious.
+ * Reject negative (including -Inf) or NaN offset. Negative is per spec,
+ * and NaN is because appropriate semantics for that seem non-obvious.
*/
- if (NUMERIC_IS_NAN(offset) || NUMERIC_SIGN(offset) == NUMERIC_NEG)
+ if (NUMERIC_IS_NAN(offset) ||
+ NUMERIC_IS_NINF(offset) ||
+ NUMERIC_SIGN(offset) == NUMERIC_NEG)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
{
result = less; /* non-NAN < NAN */
}
+
+ /*
+ * Deal with infinite offset (necessarily +Inf, at this point).
+ */
+ else if (NUMERIC_IS_SPECIAL(offset))
+ {
+ Assert(NUMERIC_IS_PINF(offset));
+ if (sub ? NUMERIC_IS_PINF(base) : NUMERIC_IS_NINF(base))
+ {
+ /*
+ * base +/- offset would produce NaN, so return true for any val
+ * (see in_range_float8_float8() for reasoning).
+ */
+ result = true;
+ }
+ else if (sub)
+ {
+ /* base - offset must be -inf */
+ if (less)
+ result = NUMERIC_IS_NINF(val); /* only -inf is <= sum */
+ else
+ result = true; /* any val is >= sum */
+ }
+ else
+ {
+ /* base + offset must be +inf */
+ if (less)
+ result = true; /* any val is <= sum */
+ else
+ result = NUMERIC_IS_PINF(val); /* only +inf is >= sum */
+ }
+ }
+
+ /*
+ * Deal with cases where val and/or base is infinite. The offset, being
+ * now known finite, cannot affect the conclusion.
+ */
+ else if (NUMERIC_IS_SPECIAL(val))
+ {
+ if (NUMERIC_IS_PINF(val))
+ {
+ if (NUMERIC_IS_PINF(base))
+ result = true; /* PINF = PINF */
+ else
+ result = !less; /* PINF > any other non-NAN */
+ }
+ else /* val must be NINF */
+ {
+ if (NUMERIC_IS_NINF(base))
+ result = true; /* NINF = NINF */
+ else
+ result = less; /* NINF < anything else */
+ }
+ }
+ else if (NUMERIC_IS_SPECIAL(base))
+ {
+ if (NUMERIC_IS_NINF(base))
+ result = !less; /* normal > NINF */
+ else
+ result = less; /* normal < PINF */
+ }
else
{
/*
int hash_len;
NumericDigit *digits;
- /* If it's NaN, don't try to hash the rest of the fields */
- if (NUMERIC_IS_NAN(key))
+ /* If it's NaN or infinity, don't try to hash the rest of the fields */
+ if (NUMERIC_IS_SPECIAL(key))
PG_RETURN_UINT32(0);
weight = NUMERIC_WEIGHT(key);
int hash_len;
NumericDigit *digits;
- if (NUMERIC_IS_NAN(key))
+ /* If it's NaN or infinity, don't try to hash the rest of the fields */
+ if (NUMERIC_IS_SPECIAL(key))
PG_RETURN_UINT64(seed);
weight = NUMERIC_WEIGHT(key);
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ return make_result(&const_nan);
+ if (NUMERIC_IS_PINF(num1))
+ {
+ if (NUMERIC_IS_NINF(num2))
+ return make_result(&const_nan); /* Inf + -Inf */
+ else
+ return make_result(&const_pinf);
+ }
+ if (NUMERIC_IS_NINF(num1))
+ {
+ if (NUMERIC_IS_PINF(num2))
+ return make_result(&const_nan); /* -Inf + Inf */
+ else
+ return make_result(&const_ninf);
+ }
+ /* by here, num1 must be finite, so num2 is not */
+ if (NUMERIC_IS_PINF(num2))
+ return make_result(&const_pinf);
+ Assert(NUMERIC_IS_NINF(num2));
+ return make_result(&const_ninf);
+ }
/*
* Unpack the values, let add_var() compute the result and return it.
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ return make_result(&const_nan);
+ if (NUMERIC_IS_PINF(num1))
+ {
+ if (NUMERIC_IS_PINF(num2))
+ return make_result(&const_nan); /* Inf - Inf */
+ else
+ return make_result(&const_pinf);
+ }
+ if (NUMERIC_IS_NINF(num1))
+ {
+ if (NUMERIC_IS_NINF(num2))
+ return make_result(&const_nan); /* -Inf - -Inf */
+ else
+ return make_result(&const_ninf);
+ }
+ /* by here, num1 must be finite, so num2 is not */
+ if (NUMERIC_IS_PINF(num2))
+ return make_result(&const_ninf);
+ Assert(NUMERIC_IS_NINF(num2));
+ return make_result(&const_pinf);
+ }
/*
* Unpack the values, let sub_var() compute the result and return it.
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ return make_result(&const_nan);
+ if (NUMERIC_IS_PINF(num1))
+ {
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ return make_result(&const_nan); /* Inf * 0 */
+ case 1:
+ return make_result(&const_pinf);
+ case -1:
+ return make_result(&const_ninf);
+ }
+ Assert(false);
+ }
+ if (NUMERIC_IS_NINF(num1))
+ {
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ return make_result(&const_nan); /* -Inf * 0 */
+ case 1:
+ return make_result(&const_ninf);
+ case -1:
+ return make_result(&const_pinf);
+ }
+ Assert(false);
+ }
+ /* by here, num1 must be finite, so num2 is not */
+ if (NUMERIC_IS_PINF(num2))
+ {
+ switch (numeric_sign_internal(num1))
+ {
+ case 0:
+ return make_result(&const_nan); /* 0 * Inf */
+ case 1:
+ return make_result(&const_pinf);
+ case -1:
+ return make_result(&const_ninf);
+ }
+ Assert(false);
+ }
+ Assert(NUMERIC_IS_NINF(num2));
+ switch (numeric_sign_internal(num1))
+ {
+ case 0:
+ return make_result(&const_nan); /* 0 * -Inf */
+ case 1:
+ return make_result(&const_ninf);
+ case -1:
+ return make_result(&const_pinf);
+ }
+ Assert(false);
+ }
/*
* Unpack the values, let mul_var() compute the result and return it.
*have_error = false;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ return make_result(&const_nan);
+ if (NUMERIC_IS_PINF(num1))
+ {
+ if (NUMERIC_IS_SPECIAL(num2))
+ return make_result(&const_nan); /* Inf / [-]Inf */
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ if (have_error)
+ {
+ *have_error = true;
+ return NULL;
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+ break;
+ case 1:
+ return make_result(&const_pinf);
+ case -1:
+ return make_result(&const_ninf);
+ }
+ Assert(false);
+ }
+ if (NUMERIC_IS_NINF(num1))
+ {
+ if (NUMERIC_IS_SPECIAL(num2))
+ return make_result(&const_nan); /* -Inf / [-]Inf */
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ if (have_error)
+ {
+ *have_error = true;
+ return NULL;
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+ break;
+ case 1:
+ return make_result(&const_ninf);
+ case -1:
+ return make_result(&const_pinf);
+ }
+ Assert(false);
+ }
+ /* by here, num1 must be finite, so num2 is not */
+
+ /*
+ * POSIX would have us return zero or minus zero if num1 is zero, and
+ * otherwise throw an underflow error. But the numeric type doesn't
+ * really do underflow, so let's just return zero.
+ */
+ return make_result(&const_zero);
+ }
/*
* Unpack the arguments
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_PINF(num1))
+ {
+ if (NUMERIC_IS_SPECIAL(num2))
+ PG_RETURN_NUMERIC(make_result(&const_nan)); /* Inf / [-]Inf */
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+ break;
+ case 1:
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ case -1:
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+ }
+ Assert(false);
+ }
+ if (NUMERIC_IS_NINF(num1))
+ {
+ if (NUMERIC_IS_SPECIAL(num2))
+ PG_RETURN_NUMERIC(make_result(&const_nan)); /* -Inf / [-]Inf */
+ switch (numeric_sign_internal(num2))
+ {
+ case 0:
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+ break;
+ case 1:
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+ case -1:
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ }
+ Assert(false);
+ }
+ /* by here, num1 must be finite, so num2 is not */
+
+ /*
+ * POSIX would have us return zero or minus zero if num1 is zero, and
+ * otherwise throw an underflow error. But the numeric type doesn't
+ * really do underflow, so let's just return zero.
+ */
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ }
/*
* Unpack the arguments
if (have_error)
*have_error = false;
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- return make_result(&const_nan);
+ /*
+ * Handle NaN and infinities. We follow POSIX fmod() on this, except that
+ * POSIX treats x-is-infinite and y-is-zero identically, raising EDOM and
+ * returning NaN. We choose to throw error only for y-is-zero.
+ */
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ return make_result(&const_nan);
+ if (NUMERIC_IS_INF(num1))
+ {
+ if (numeric_sign_internal(num2) == 0)
+ {
+ if (have_error)
+ {
+ *have_error = true;
+ return NULL;
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_DIVISION_BY_ZERO),
+ errmsg("division by zero")));
+ }
+ /* Inf % any nonzero = NaN */
+ return make_result(&const_nan);
+ }
+ /* num2 must be [-]Inf; result is num1 regardless of sign of num2 */
+ return duplicate_numeric(num1);
+ }
init_var_from_num(num1, &arg1);
init_var_from_num(num2, &arg2);
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
/*
* Compute the result and return it
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities: we consider the result to be NaN in all such
+ * cases.
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
Numeric res;
/*
- * Handle NaN
+ * Handle NaN and infinities: we consider the result to be NaN in all such
+ * cases.
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
PG_RETURN_NUMERIC(make_result(&const_nan));
/*
int rscale;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ /* error should match that in sqrt_var() */
+ if (NUMERIC_IS_NINF(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
+ errmsg("cannot take square root of a negative number")));
+ /* For NAN or PINF, just duplicate the input */
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
+ }
/*
* Unpack the argument and determine the result scale. We choose a scale
double val;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ /* Per POSIX, exp(-Inf) is zero */
+ if (NUMERIC_IS_NINF(num))
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ /* For NAN or PINF, just duplicate the input */
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
+ }
/*
* Unpack the argument and determine the result scale. We choose a scale
int rscale;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_NINF(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
+ errmsg("cannot take logarithm of a negative number")));
+ /* For NAN or PINF, just duplicate the input */
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
+ }
init_var_from_num(num, &arg);
init_var(&result);
NumericVar result;
/*
- * Handle NaN
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
+ {
+ int sign1,
+ sign2;
+
+ if (NUMERIC_IS_NAN(num1) || NUMERIC_IS_NAN(num2))
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ /* fail on negative inputs including -Inf, as log_var would */
+ sign1 = numeric_sign_internal(num1);
+ sign2 = numeric_sign_internal(num2);
+ if (sign1 < 0 || sign2 < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
+ errmsg("cannot take logarithm of a negative number")));
+ /* fail on zero inputs, as log_var would */
+ if (sign1 == 0 || sign2 == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_LOG),
+ errmsg("cannot take logarithm of zero")));
+ if (NUMERIC_IS_PINF(num1))
+ {
+ /* log(Inf, Inf) reduces to Inf/Inf, so it's NaN */
+ if (NUMERIC_IS_PINF(num2))
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ /* log(Inf, finite-positive) is zero (we don't throw underflow) */
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ }
+ Assert(NUMERIC_IS_PINF(num2));
+ /* log(finite-positive, Inf) is Inf */
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ }
/*
* Initialize things
/*
* numeric_power() -
*
- * Raise b to the power of x
+ * Raise x to the power of y
*/
Datum
numeric_power(PG_FUNCTION_ARGS)
Numeric res;
NumericVar arg1;
NumericVar arg2;
- NumericVar arg2_trunc;
NumericVar result;
+ int sign1,
+ sign2;
/*
- * Handle NaN cases. We follow the POSIX spec for pow(3), which says that
- * NaN ^ 0 = 1, and 1 ^ NaN = 1, while all other cases with NaN inputs
- * yield NaN (with no error).
+ * Handle NaN and infinities
*/
- if (NUMERIC_IS_NAN(num1))
+ if (NUMERIC_IS_SPECIAL(num1) || NUMERIC_IS_SPECIAL(num2))
{
- if (!NUMERIC_IS_NAN(num2))
+ /*
+ * We follow the POSIX spec for pow(3), which says that NaN ^ 0 = 1,
+ * and 1 ^ NaN = 1, while all other cases with NaN inputs yield NaN
+ * (with no error).
+ */
+ if (NUMERIC_IS_NAN(num1))
+ {
+ if (!NUMERIC_IS_SPECIAL(num2))
+ {
+ init_var_from_num(num2, &arg2);
+ if (cmp_var(&arg2, &const_zero) == 0)
+ PG_RETURN_NUMERIC(make_result(&const_one));
+ }
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ }
+ if (NUMERIC_IS_NAN(num2))
+ {
+ if (!NUMERIC_IS_SPECIAL(num1))
+ {
+ init_var_from_num(num1, &arg1);
+ if (cmp_var(&arg1, &const_one) == 0)
+ PG_RETURN_NUMERIC(make_result(&const_one));
+ }
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ }
+ /* At least one input is infinite, but error rules still apply */
+ sign1 = numeric_sign_internal(num1);
+ sign2 = numeric_sign_internal(num2);
+ if (sign1 == 0 && sign2 < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
+ errmsg("zero raised to a negative power is undefined")));
+ if (sign1 < 0 && !numeric_is_integral(num2))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
+ errmsg("a negative number raised to a non-integer power yields a complex result")));
+
+ /*
+ * POSIX gives this series of rules for pow(3) with infinite inputs:
+ *
+ * For any value of y, if x is +1, 1.0 shall be returned.
+ */
+ if (!NUMERIC_IS_SPECIAL(num1))
{
- init_var_from_num(num2, &arg2);
- if (cmp_var(&arg2, &const_zero) == 0)
+ init_var_from_num(num1, &arg1);
+ if (cmp_var(&arg1, &const_one) == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
}
- PG_RETURN_NUMERIC(make_result(&const_nan));
- }
- if (NUMERIC_IS_NAN(num2))
- {
- init_var_from_num(num1, &arg1);
- if (cmp_var(&arg1, &const_one) == 0)
+
+ /*
+ * For any value of x, if y is [-]0, 1.0 shall be returned.
+ */
+ if (sign2 == 0)
PG_RETURN_NUMERIC(make_result(&const_one));
- PG_RETURN_NUMERIC(make_result(&const_nan));
- }
- /*
- * Initialize things
- */
- init_var(&arg2_trunc);
- init_var(&result);
- init_var_from_num(num1, &arg1);
- init_var_from_num(num2, &arg2);
+ /*
+ * For any odd integer value of y > 0, if x is [-]0, [-]0 shall be
+ * returned. For y > 0 and not an odd integer, if x is [-]0, +0 shall
+ * be returned. (Since we don't deal in minus zero, we need not
+ * distinguish these two cases.)
+ */
+ if (sign1 == 0 && sign2 > 0)
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+
+ /*
+ * If x is -1, and y is [-]Inf, 1.0 shall be returned.
+ *
+ * For |x| < 1, if y is -Inf, +Inf shall be returned.
+ *
+ * For |x| > 1, if y is -Inf, +0 shall be returned.
+ *
+ * For |x| < 1, if y is +Inf, +0 shall be returned.
+ *
+ * For |x| > 1, if y is +Inf, +Inf shall be returned.
+ */
+ if (NUMERIC_IS_INF(num2))
+ {
+ bool abs_x_gt_one;
+
+ if (NUMERIC_IS_SPECIAL(num1))
+ abs_x_gt_one = true; /* x is either Inf or -Inf */
+ else
+ {
+ init_var_from_num(num1, &arg1);
+ if (cmp_var(&arg1, &const_minus_one) == 0)
+ PG_RETURN_NUMERIC(make_result(&const_one));
+ arg1.sign = NUMERIC_POS; /* now arg1 = abs(x) */
+ abs_x_gt_one = (cmp_var(&arg1, &const_one) > 0);
+ }
+ if (abs_x_gt_one == (sign2 > 0))
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ else
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ }
+
+ /*
+ * For y < 0, if x is +Inf, +0 shall be returned.
+ *
+ * For y > 0, if x is +Inf, +Inf shall be returned.
+ */
+ if (NUMERIC_IS_PINF(num1))
+ {
+ if (sign2 > 0)
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ else
+ PG_RETURN_NUMERIC(make_result(&const_zero));
+ }
+
+ Assert(NUMERIC_IS_NINF(num1));
+
+ /*
+ * For y an odd integer < 0, if x is -Inf, -0 shall be returned. For
+ * y < 0 and not an odd integer, if x is -Inf, +0 shall be returned.
+ * (Again, we need not distinguish these two cases.)
+ */
+ if (sign2 < 0)
+ PG_RETURN_NUMERIC(make_result(&const_zero));
- set_var_from_var(&arg2, &arg2_trunc);
- trunc_var(&arg2_trunc, 0);
+ /*
+ * For y an odd integer > 0, if x is -Inf, -Inf shall be returned. For
+ * y > 0 and not an odd integer, if x is -Inf, +Inf shall be returned.
+ */
+ init_var_from_num(num2, &arg2);
+ if (arg2.ndigits > 0 && arg2.ndigits == arg2.weight + 1 &&
+ (arg2.digits[arg2.ndigits - 1] & 1))
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+ else
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ }
/*
* The SQL spec requires that we emit a particular SQLSTATE error code for
* certain error conditions. Specifically, we don't return a
* divide-by-zero error code for 0 ^ -1.
*/
- if (cmp_var(&arg1, &const_zero) == 0 &&
- cmp_var(&arg2, &const_zero) < 0)
+ sign1 = numeric_sign_internal(num1);
+ sign2 = numeric_sign_internal(num2);
+
+ if (sign1 == 0 && sign2 < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("zero raised to a negative power is undefined")));
- if (cmp_var(&arg1, &const_zero) < 0 &&
- cmp_var(&arg2, &arg2_trunc) != 0)
+ if (sign1 < 0 && !numeric_is_integral(num2))
ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION),
errmsg("a negative number raised to a non-integer power yields a complex result")));
+ /*
+ * Initialize things
+ */
+ init_var(&result);
+ init_var_from_num(num1, &arg1);
+ init_var_from_num(num2, &arg2);
+
/*
* Call power_var() to compute and return the result; note it handles
* scale selection itself.
res = make_result(&result);
free_var(&result);
- free_var(&arg2_trunc);
PG_RETURN_NUMERIC(res);
}
{
Numeric num = PG_GETARG_NUMERIC(0);
- if (NUMERIC_IS_NAN(num))
+ if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NULL();
PG_RETURN_INT32(NUMERIC_DSCALE(num));
NumericVar arg;
int min_scale;
- if (NUMERIC_IS_NAN(num))
+ if (NUMERIC_IS_SPECIAL(num))
PG_RETURN_NULL();
init_var_from_num(num, &arg);
Numeric res;
NumericVar result;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (NUMERIC_IS_SPECIAL(num))
+ PG_RETURN_NUMERIC(duplicate_numeric(num));
init_var_from_num(num, &result);
result.dscale = get_min_scale(&result);
if (have_error)
*have_error = false;
- /* XXX would it be better to return NULL? */
- if (NUMERIC_IS_NAN(num))
+ if (NUMERIC_IS_SPECIAL(num))
{
if (have_error)
{
}
else
{
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert NaN to integer")));
+ if (NUMERIC_IS_NAN(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert NaN to integer")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to integer")));
}
}
NumericVar x;
int64 result;
- /* XXX would it be better to return NULL? */
- if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert NaN to bigint")));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_NAN(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert NaN to bigint")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to bigint")));
+ }
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
int64 val;
int16 result;
- /* XXX would it be better to return NULL? */
- if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert NaN to smallint")));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_NAN(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert NaN to smallint")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to smallint")));
+ }
/* Convert to variable format and thence to int8 */
init_var_from_num(num, &x);
PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert infinity to numeric")));
+ {
+ if (val < 0)
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+ else
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ }
snprintf(buf, sizeof(buf), "%.*g", DBL_DIG, val);
char *tmp;
Datum result;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_FLOAT8(get_float8_nan());
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ PG_RETURN_FLOAT8(get_float8_infinity());
+ else if (NUMERIC_IS_NINF(num))
+ PG_RETURN_FLOAT8(-get_float8_infinity());
+ else
+ PG_RETURN_FLOAT8(get_float8_nan());
+ }
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
Numeric num = PG_GETARG_NUMERIC(0);
double val;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_FLOAT8(get_float8_nan());
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ val = HUGE_VAL;
+ else if (NUMERIC_IS_NINF(num))
+ val = -HUGE_VAL;
+ else
+ val = get_float8_nan();
+ }
+ else
+ {
+ NumericVar x;
- val = numeric_to_double_no_overflow(num);
+ init_var_from_num(num, &x);
+ val = numericvar_to_double_no_overflow(&x);
+ }
PG_RETURN_FLOAT8(val);
}
PG_RETURN_NUMERIC(make_result(&const_nan));
if (isinf(val))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert infinity to numeric")));
+ {
+ if (val < 0)
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+ else
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ }
snprintf(buf, sizeof(buf), "%.*g", FLT_DIG, val);
char *tmp;
Datum result;
- if (NUMERIC_IS_NAN(num))
- PG_RETURN_FLOAT4(get_float4_nan());
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_PINF(num))
+ PG_RETURN_FLOAT4(get_float4_infinity());
+ else if (NUMERIC_IS_NINF(num))
+ PG_RETURN_FLOAT4(-get_float4_infinity());
+ else
+ PG_RETURN_FLOAT4(get_float4_nan());
+ }
tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
NumericVar x;
XLogRecPtr result;
- if (NUMERIC_IS_NAN(num))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("cannot convert NaN to pg_lsn")));
+ if (NUMERIC_IS_SPECIAL(num))
+ {
+ if (NUMERIC_IS_NAN(num))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert NaN to pg_lsn")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert infinity to pg_lsn")));
+ }
/* Convert to variable format and thence to pg_lsn */
init_var_from_num(num, &x);
NumericSumAccum sumX2; /* sum of squares of processed numbers */
int maxScale; /* maximum scale seen so far */
int64 maxScaleCount; /* number of values seen with maximum scale */
- int64 NaNcount; /* count of NaN values (not included in N!) */
+ /* These counts are *not* included in N! Use NA_TOTAL_COUNT() as needed */
+ int64 NaNcount; /* count of NaN values */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
} NumericAggState;
+#define NA_TOTAL_COUNT(na) \
+ ((na)->N + (na)->NaNcount + (na)->pInfcount + (na)->nInfcount)
+
/*
* Prepare state data for a numeric aggregate function that needs to compute
* sum, count and optionally sum of squares of the input.
NumericVar X2;
MemoryContext old_context;
- /* Count NaN inputs separately from all else */
- if (NUMERIC_IS_NAN(newval))
+ /* Count NaN/infinity inputs separately from all else */
+ if (NUMERIC_IS_SPECIAL(newval))
{
- state->NaNcount++;
+ if (NUMERIC_IS_PINF(newval))
+ state->pInfcount++;
+ else if (NUMERIC_IS_NINF(newval))
+ state->nInfcount++;
+ else
+ state->NaNcount++;
return;
}
NumericVar X2;
MemoryContext old_context;
- /* Count NaN inputs separately from all else */
- if (NUMERIC_IS_NAN(newval))
+ /* Count NaN/infinity inputs separately from all else */
+ if (NUMERIC_IS_SPECIAL(newval))
{
- state->NaNcount--;
+ if (NUMERIC_IS_PINF(newval))
+ state->pInfcount--;
+ else if (NUMERIC_IS_NINF(newval))
+ state->nInfcount--;
+ else
+ state->NaNcount--;
return true;
}
state1 = makeNumericAggStateCurrentContext(true);
state1->N = state2->N;
state1->NaNcount = state2->NaNcount;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
state1->N += state2->N;
state1->NaNcount += state2->NaNcount;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
if (state2->N > 0)
{
state1 = makeNumericAggStateCurrentContext(false);
state1->N = state2->N;
state1->NaNcount = state2->NaNcount;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
state1->maxScale = state2->maxScale;
state1->maxScaleCount = state2->maxScaleCount;
state1->N += state2->N;
state1->NaNcount += state2->NaNcount;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
if (state2->N > 0)
{
/* NaNcount */
pq_sendint64(&buf, state->NaNcount);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
/* NaNcount */
result->NaNcount = pq_getmsgint64(&buf);
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
pq_getmsgend(&buf);
pfree(buf.data);
/* NaNcount */
pq_sendint64(&buf, state->NaNcount);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
/* NaNcount */
result->NaNcount = pq_getmsgint64(&buf);
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
pq_getmsgend(&buf);
pfree(buf.data);
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
- if (state == NULL || (state->N + state->NaNcount) == 0)
+ if (state == NULL || NA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan));
+ /* adding plus and minus infinities gives NaN */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (state->pInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ if (state->nInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+
N_datum = DirectFunctionCall1(int8_numeric, Int64GetDatum(state->N));
init_var(&sumX_var);
state = PG_ARGISNULL(0) ? NULL : (NumericAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
- if (state == NULL || (state->N + state->NaNcount) == 0)
+ if (state == NULL || NA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
if (state->NaNcount > 0) /* there was at least one NaN input */
PG_RETURN_NUMERIC(make_result(&const_nan));
+ /* adding plus and minus infinities gives NaN */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_nan));
+ if (state->pInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_pinf));
+ if (state->nInfcount > 0)
+ PG_RETURN_NUMERIC(make_result(&const_ninf));
+
init_var(&sumX_var);
accum_sum_final(&state->sumX, &sumX_var);
result = make_result(&sumX_var);
/*
* Sample stddev and variance are undefined when N <= 1; population stddev
* is undefined when N == 0. Return NULL in either case (note that NaNs
- * count as normal inputs for this purpose).
+ * and infinities count as normal inputs for this purpose).
*/
- if (state == NULL || (totCount = state->N + state->NaNcount) == 0)
+ if (state == NULL || (totCount = NA_TOTAL_COUNT(state)) == 0)
{
*is_null = true;
return NULL;
*is_null = false;
/*
- * Deal with NaN inputs.
+ * Deal with NaN and infinity cases. By analogy to the behavior of the
+ * float8 functions, any infinity input produces NaN output.
*/
- if (state->NaNcount > 0)
+ if (state->NaNcount > 0 || state->pInfcount > 0 || state->nInfcount > 0)
return make_result(&const_nan);
/* OK, normal calculation applies */
case NUMERIC_NAN:
printf("NaN");
break;
+ case NUMERIC_PINF:
+ printf("Infinity");
+ break;
+ case NUMERIC_NINF:
+ printf("-Infinity");
+ break;
default:
printf("SIGN=0x%x", NUMERIC_SIGN(num));
break;
case NUMERIC_NAN:
printf("NaN");
break;
+ case NUMERIC_PINF:
+ printf("Infinity");
+ break;
+ case NUMERIC_NINF:
+ printf("-Infinity");
+ break;
default:
printf("SIGN=0x%x", var->sign);
break;
*
* Local functions follow
*
- * In general, these do not support NaNs --- callers must eliminate
- * the possibility of NaN first. (make_result() is an exception.)
+ * In general, these do not support "special" (NaN or infinity) inputs;
+ * callers should handle those possibilities first.
+ * (There are one or two exceptions, noted in their header comments.)
*
* ----------------------------------------------------------------------
*/
*
* Parse a string and put the number into a variable
*
- * This function does not handle leading or trailing spaces, and it doesn't
- * accept "NaN" either. It returns the end+1 position so that caller can
- * check for trailing spaces/garbage if deemed necessary.
+ * This function does not handle leading or trailing spaces. It returns
+ * the end+1 position parsed, so that caller can check for trailing
+ * spaces/garbage if deemed necessary.
*
* cp is the place to actually start parsing; str is what to use in error
* reports. (Typically cp would be the same except advanced over spaces.)
}
+/*
+ * duplicate_numeric() - copy a packed-format Numeric
+ *
+ * This will handle NaN and Infinity cases.
+ */
+static Numeric
+duplicate_numeric(Numeric num)
+{
+ Numeric res;
+
+ res = (Numeric) palloc(VARSIZE(num));
+ memcpy(res, num, VARSIZE(num));
+ return res;
+}
+
/*
* make_result_opt_error() -
*
* Create the packed db numeric format in palloc()'d memory from
- * a variable. If "*have_error" flag is provided, on error it's set to
- * true, NULL returned. This is helpful when caller need to handle errors
- * by itself.
+ * a variable. This will handle NaN and Infinity cases.
+ *
+ * If "have_error" isn't NULL, on overflow *have_error is set to true and
+ * NULL is returned. This is helpful when caller needs to handle errors.
*/
static Numeric
make_result_opt_error(const NumericVar *var, bool *have_error)
if (have_error)
*have_error = false;
- if (sign == NUMERIC_NAN)
+ if ((sign & NUMERIC_SIGN_MASK) == NUMERIC_SPECIAL)
{
+ /*
+ * Verify valid special value. This could be just an Assert, perhaps,
+ * but it seems worthwhile to expend a few cycles to ensure that we
+ * never write any nonzero reserved bits to disk.
+ */
+ if (!(sign == NUMERIC_NAN ||
+ sign == NUMERIC_PINF ||
+ sign == NUMERIC_NINF))
+ elog(ERROR, "invalid numeric sign value 0x%x", sign);
+
result = (Numeric) palloc(NUMERIC_HDRSZ_SHORT);
SET_VARSIZE(result, NUMERIC_HDRSZ_SHORT);
- result->choice.n_header = NUMERIC_NAN;
+ result->choice.n_header = sign;
/* the header word is all we need */
dump_numeric("make_result()", result);
/*
* apply_typmod() -
*
- * Do bounds checking and rounding according to the attributes
- * typmod field.
+ * Do bounds checking and rounding according to the specified typmod.
+ * Note that this is only applied to normal finite values.
*/
static void
apply_typmod(NumericVar *var, int32 typmod)
}
}
+/*
+ * apply_typmod_special() -
+ *
+ * Do bounds checking according to the specified typmod, for an Inf or NaN.
+ * For convenience of most callers, the value is presented in packed form.
+ */
+static void
+apply_typmod_special(Numeric num, int32 typmod)
+{
+ int precision;
+ int scale;
+
+ Assert(NUMERIC_IS_SPECIAL(num)); /* caller error if not */
+
+ /*
+ * NaN is allowed regardless of the typmod; that's rather dubious perhaps,
+ * but it's a longstanding behavior. Inf is rejected if we have any
+ * typmod restriction, since an infinity shouldn't be claimed to fit in
+ * any finite number of digits.
+ */
+ if (NUMERIC_IS_NAN(num))
+ return;
+
+ /* Do nothing if we have a default typmod (-1) */
+ if (typmod < (int32) (VARHDRSZ))
+ return;
+
+ typmod -= VARHDRSZ;
+ precision = (typmod >> 16) & 0xffff;
+ scale = typmod & 0xffff;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("numeric field overflow"),
+ errdetail("A field with precision %d, scale %d cannot hold an infinite value.",
+ precision, scale)));
+}
+
+
/*
* Convert numeric to int8, rounding if needed.
*
#endif
/*
- * Convert numeric to float8; if out of range, return +/- HUGE_VAL
+ * Convert a NumericVar to float8; if out of range, return +/- HUGE_VAL
*/
static double
-numeric_to_double_no_overflow(Numeric num)
-{
- char *tmp;
- double val;
- char *endptr;
-
- tmp = DatumGetCString(DirectFunctionCall1(numeric_out,
- NumericGetDatum(num)));
-
- /* unlike float8in, we ignore ERANGE from strtod */
- val = strtod(tmp, &endptr);
- if (*endptr != '\0')
- {
- /* shouldn't happen ... */
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
- errmsg("invalid input syntax for type %s: \"%s\"",
- "double precision", tmp)));
- }
-
- pfree(tmp);
-
- return val;
-}
-
-/* As above, but work from a NumericVar */
-static double
numericvar_to_double_no_overflow(const NumericVar *var)
{
char *tmp;
-----+--------+----------
(0 rows)
+-- ******************************
+-- * Check behavior with Inf and NaN inputs. It's easiest to handle these
+-- * separately from the num_data framework used above, because some input
+-- * combinations will throw errors.
+-- ******************************
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
+SELECT x1, x2,
+ x1 + x2 AS sum,
+ x1 - x2 AS diff,
+ x1 * x2 AS prod
+FROM v AS v1(x1), v AS v2(x2);
+ x1 | x2 | sum | diff | prod
+-----------+-----------+-----------+-----------+-----------
+ 0 | 0 | 0 | 0 | 0
+ 0 | 1 | 1 | -1 | 0
+ 0 | -1 | -1 | 1 | 0
+ 0 | 4.2 | 4.2 | -4.2 | 0.0
+ 0 | Infinity | Infinity | -Infinity | NaN
+ 0 | -Infinity | -Infinity | Infinity | NaN
+ 0 | NaN | NaN | NaN | NaN
+ 1 | 0 | 1 | 1 | 0
+ 1 | 1 | 2 | 0 | 1
+ 1 | -1 | 0 | 2 | -1
+ 1 | 4.2 | 5.2 | -3.2 | 4.2
+ 1 | Infinity | Infinity | -Infinity | Infinity
+ 1 | -Infinity | -Infinity | Infinity | -Infinity
+ 1 | NaN | NaN | NaN | NaN
+ -1 | 0 | -1 | -1 | 0
+ -1 | 1 | 0 | -2 | -1
+ -1 | -1 | -2 | 0 | 1
+ -1 | 4.2 | 3.2 | -5.2 | -4.2
+ -1 | Infinity | Infinity | -Infinity | -Infinity
+ -1 | -Infinity | -Infinity | Infinity | Infinity
+ -1 | NaN | NaN | NaN | NaN
+ 4.2 | 0 | 4.2 | 4.2 | 0.0
+ 4.2 | 1 | 5.2 | 3.2 | 4.2
+ 4.2 | -1 | 3.2 | 5.2 | -4.2
+ 4.2 | 4.2 | 8.4 | 0.0 | 17.64
+ 4.2 | Infinity | Infinity | -Infinity | Infinity
+ 4.2 | -Infinity | -Infinity | Infinity | -Infinity
+ 4.2 | NaN | NaN | NaN | NaN
+ Infinity | 0 | Infinity | Infinity | NaN
+ Infinity | 1 | Infinity | Infinity | Infinity
+ Infinity | -1 | Infinity | Infinity | -Infinity
+ Infinity | 4.2 | Infinity | Infinity | Infinity
+ Infinity | Infinity | Infinity | NaN | Infinity
+ Infinity | -Infinity | NaN | Infinity | -Infinity
+ Infinity | NaN | NaN | NaN | NaN
+ -Infinity | 0 | -Infinity | -Infinity | NaN
+ -Infinity | 1 | -Infinity | -Infinity | -Infinity
+ -Infinity | -1 | -Infinity | -Infinity | Infinity
+ -Infinity | 4.2 | -Infinity | -Infinity | -Infinity
+ -Infinity | Infinity | NaN | -Infinity | -Infinity
+ -Infinity | -Infinity | -Infinity | NaN | Infinity
+ -Infinity | NaN | NaN | NaN | NaN
+ NaN | 0 | NaN | NaN | NaN
+ NaN | 1 | NaN | NaN | NaN
+ NaN | -1 | NaN | NaN | NaN
+ NaN | 4.2 | NaN | NaN | NaN
+ NaN | Infinity | NaN | NaN | NaN
+ NaN | -Infinity | NaN | NaN | NaN
+ NaN | NaN | NaN | NaN | NaN
+(49 rows)
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
+SELECT x1, x2,
+ x1 / x2 AS quot,
+ x1 % x2 AS mod,
+ div(x1, x2) AS div
+FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0;
+ x1 | x2 | quot | mod | div
+-----------+-----------+-------------------------+------+-----------
+ 0 | 1 | 0.00000000000000000000 | 0 | 0
+ 1 | 1 | 1.00000000000000000000 | 0 | 1
+ -1 | 1 | -1.00000000000000000000 | 0 | -1
+ 4.2 | 1 | 4.2000000000000000 | 0.2 | 4
+ Infinity | 1 | Infinity | NaN | Infinity
+ -Infinity | 1 | -Infinity | NaN | -Infinity
+ NaN | 1 | NaN | NaN | NaN
+ 0 | -1 | 0.00000000000000000000 | 0 | 0
+ 1 | -1 | -1.00000000000000000000 | 0 | -1
+ -1 | -1 | 1.00000000000000000000 | 0 | 1
+ 4.2 | -1 | -4.2000000000000000 | 0.2 | -4
+ Infinity | -1 | -Infinity | NaN | -Infinity
+ -Infinity | -1 | Infinity | NaN | Infinity
+ NaN | -1 | NaN | NaN | NaN
+ 0 | 4.2 | 0.00000000000000000000 | 0.0 | 0
+ 1 | 4.2 | 0.23809523809523809524 | 1.0 | 0
+ -1 | 4.2 | -0.23809523809523809524 | -1.0 | 0
+ 4.2 | 4.2 | 1.00000000000000000000 | 0.0 | 1
+ Infinity | 4.2 | Infinity | NaN | Infinity
+ -Infinity | 4.2 | -Infinity | NaN | -Infinity
+ NaN | 4.2 | NaN | NaN | NaN
+ 0 | Infinity | 0 | 0 | 0
+ 1 | Infinity | 0 | 1 | 0
+ -1 | Infinity | 0 | -1 | 0
+ 4.2 | Infinity | 0 | 4.2 | 0
+ Infinity | Infinity | NaN | NaN | NaN
+ -Infinity | Infinity | NaN | NaN | NaN
+ NaN | Infinity | NaN | NaN | NaN
+ 0 | -Infinity | 0 | 0 | 0
+ 1 | -Infinity | 0 | 1 | 0
+ -1 | -Infinity | 0 | -1 | 0
+ 4.2 | -Infinity | 0 | 4.2 | 0
+ Infinity | -Infinity | NaN | NaN | NaN
+ -Infinity | -Infinity | NaN | NaN | NaN
+ NaN | -Infinity | NaN | NaN | NaN
+ 0 | NaN | NaN | NaN | NaN
+ 1 | NaN | NaN | NaN | NaN
+ -1 | NaN | NaN | NaN | NaN
+ 4.2 | NaN | NaN | NaN | NaN
+ Infinity | NaN | NaN | NaN | NaN
+ -Infinity | NaN | NaN | NaN | NaN
+ NaN | NaN | NaN | NaN | NaN
+(42 rows)
+
+SELECT 'inf'::numeric / '0';
+ERROR: division by zero
+SELECT '-inf'::numeric / '0';
+ERROR: division by zero
+SELECT 'nan'::numeric / '0';
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT '0'::numeric / '0';
+ERROR: division by zero
+SELECT 'inf'::numeric % '0';
+ERROR: division by zero
+SELECT '-inf'::numeric % '0';
+ERROR: division by zero
+SELECT 'nan'::numeric % '0';
+ ?column?
+----------
+ NaN
+(1 row)
+
+SELECT '0'::numeric % '0';
+ERROR: division by zero
+SELECT div('inf'::numeric, '0');
+ERROR: division by zero
+SELECT div('-inf'::numeric, '0');
+ERROR: division by zero
+SELECT div('nan'::numeric, '0');
+ div
+-----
+ NaN
+(1 row)
+
+SELECT div('0'::numeric, '0');
+ERROR: division by zero
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
+SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc
+FROM v;
+ x | minusx | abs | floor | ceil | sign | inc
+-----------+-----------+----------+-----------+-----------+------+-----------
+ 0 | 0 | 0 | 0 | 0 | 0 | 1
+ 1 | -1 | 1 | 1 | 1 | 1 | 2
+ -1 | 1 | 1 | -1 | -1 | -1 | 0
+ 4.2 | -4.2 | 4.2 | 4 | 5 | 1 | 5.2
+ -7.777 | 7.777 | 7.777 | -8 | -7 | -1 | -6.777
+ Infinity | -Infinity | Infinity | Infinity | Infinity | 1 | Infinity
+ -Infinity | Infinity | Infinity | -Infinity | -Infinity | -1 | -Infinity
+ NaN | NaN | NaN | NaN | NaN | NaN | NaN
+(8 rows)
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
+SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1
+FROM v;
+ x | round | round1 | trunc | trunc1
+-----------+-----------+-----------+-----------+-----------
+ 0 | 0 | 0.0 | 0 | 0.0
+ 1 | 1 | 1.0 | 1 | 1.0
+ -1 | -1 | -1.0 | -1 | -1.0
+ 4.2 | 4 | 4.2 | 4 | 4.2
+ -7.777 | -8 | -7.8 | -7 | -7.7
+ Infinity | Infinity | Infinity | Infinity | Infinity
+ -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+ NaN | NaN | NaN | NaN | NaN
+(8 rows)
+
+-- the large values fall into the numeric abbreviation code's maximal classes
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'),
+ ('inf'),('-inf'),('nan'),
+ ('inf'),('-inf'),('nan'))
+SELECT substring(x::text, 1, 32)
+FROM v ORDER BY x;
+ substring
+----------------------------------
+ -Infinity
+ -Infinity
+ -1000000000000000000000000000000
+ -7.777
+ -1
+ 0
+ 1
+ 4.2
+ 10000000000000000000000000000000
+ Infinity
+ Infinity
+ NaN
+ NaN
+(13 rows)
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan'))
+SELECT x, sqrt(x)
+FROM v;
+ x | sqrt
+----------+-------------------
+ 0 | 0.000000000000000
+ 1 | 1.000000000000000
+ 4.2 | 2.049390153191920
+ Infinity | Infinity
+ NaN | NaN
+(5 rows)
+
+SELECT sqrt('-1'::numeric);
+ERROR: cannot take square root of a negative number
+SELECT sqrt('-inf'::numeric);
+ERROR: cannot take square root of a negative number
+WITH v(x) AS
+ (VALUES('1'::numeric),('4.2'),('inf'),('nan'))
+SELECT x,
+ log(x),
+ log10(x),
+ ln(x)
+FROM v;
+ x | log | log10 | ln
+----------+--------------------+--------------------+--------------------
+ 1 | 0.0000000000000000 | 0.0000000000000000 | 0.0000000000000000
+ 4.2 | 0.6232492903979005 | 0.6232492903979005 | 1.4350845252893226
+ Infinity | Infinity | Infinity | Infinity
+ NaN | NaN | NaN | NaN
+(4 rows)
+
+SELECT ln('0'::numeric);
+ERROR: cannot take logarithm of zero
+SELECT ln('-1'::numeric);
+ERROR: cannot take logarithm of a negative number
+SELECT ln('-inf'::numeric);
+ERROR: cannot take logarithm of a negative number
+WITH v(x) AS
+ (VALUES('2'::numeric),('4.2'),('inf'),('nan'))
+SELECT x1, x2,
+ log(x1, x2)
+FROM v AS v1(x1), v AS v2(x2);
+ x1 | x2 | log
+----------+----------+--------------------
+ 2 | 2 | 1.0000000000000000
+ 2 | 4.2 | 2.0703893278913979
+ 2 | Infinity | Infinity
+ 2 | NaN | NaN
+ 4.2 | 2 | 0.4830009440873890
+ 4.2 | 4.2 | 1.0000000000000000
+ 4.2 | Infinity | Infinity
+ 4.2 | NaN | NaN
+ Infinity | 2 | 0
+ Infinity | 4.2 | 0
+ Infinity | Infinity | NaN
+ Infinity | NaN | NaN
+ NaN | 2 | NaN
+ NaN | 4.2 | NaN
+ NaN | Infinity | NaN
+ NaN | NaN | NaN
+(16 rows)
+
+SELECT log('0'::numeric, '10');
+ERROR: cannot take logarithm of zero
+SELECT log('10'::numeric, '0');
+ERROR: cannot take logarithm of zero
+SELECT log('-inf'::numeric, '10');
+ERROR: cannot take logarithm of a negative number
+SELECT log('10'::numeric, '-inf');
+ERROR: cannot take logarithm of a negative number
+SELECT log('inf'::numeric, '0');
+ERROR: cannot take logarithm of zero
+SELECT log('inf'::numeric, '-inf');
+ERROR: cannot take logarithm of a negative number
+SELECT log('-inf'::numeric, 'inf');
+ERROR: cannot take logarithm of a negative number
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan'))
+SELECT x1, x2,
+ power(x1, x2)
+FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0;
+ x1 | x2 | power
+----------+----------+---------------------
+ 0 | 0 | 1.0000000000000000
+ 0 | 1 | 0.0000000000000000
+ 0 | 2 | 0.0000000000000000
+ 0 | 4.2 | 0.0000000000000000
+ 0 | Infinity | 0
+ 0 | NaN | NaN
+ 1 | 0 | 1.0000000000000000
+ 1 | 1 | 1.0000000000000000
+ 1 | 2 | 1.0000000000000000
+ 1 | 4.2 | 1.0000000000000000
+ 1 | Infinity | 1
+ 1 | NaN | 1
+ 2 | 0 | 1.0000000000000000
+ 2 | 1 | 2.0000000000000000
+ 2 | 2 | 4.0000000000000000
+ 2 | 4.2 | 18.379173679952560
+ 2 | Infinity | Infinity
+ 2 | NaN | NaN
+ 4.2 | 0 | 1.0000000000000000
+ 4.2 | 1 | 4.2000000000000000
+ 4.2 | 2 | 17.6400000000000000
+ 4.2 | 4.2 | 414.61691860129675
+ 4.2 | Infinity | Infinity
+ 4.2 | NaN | NaN
+ Infinity | 0 | 1
+ Infinity | 1 | Infinity
+ Infinity | 2 | Infinity
+ Infinity | 4.2 | Infinity
+ Infinity | Infinity | Infinity
+ Infinity | NaN | NaN
+ NaN | 0 | 1
+ NaN | 1 | NaN
+ NaN | 2 | NaN
+ NaN | 4.2 | NaN
+ NaN | Infinity | NaN
+ NaN | NaN | NaN
+(36 rows)
+
+SELECT power('0'::numeric, '-1');
+ERROR: zero raised to a negative power is undefined
+SELECT power('0'::numeric, '-inf');
+ERROR: zero raised to a negative power is undefined
+SELECT power('-1'::numeric, 'inf');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power('-2'::numeric, '3');
+ power
+---------------------
+ -8.0000000000000000
+(1 row)
+
+SELECT power('-2'::numeric, '3.3');
+ERROR: a negative number raised to a non-integer power yields a complex result
+SELECT power('-2'::numeric, '-1');
+ power
+---------------------
+ -0.5000000000000000
+(1 row)
+
+SELECT power('-2'::numeric, '-1.5');
+ERROR: a negative number raised to a non-integer power yields a complex result
+SELECT power('-2'::numeric, 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power('-2'::numeric, '-inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power('inf'::numeric, '-2');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power('inf'::numeric, '-inf');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power('-inf'::numeric, '2');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power('-inf'::numeric, '3');
+ power
+-----------
+ -Infinity
+(1 row)
+
+SELECT power('-inf'::numeric, '4.5');
+ERROR: a negative number raised to a non-integer power yields a complex result
+SELECT power('-inf'::numeric, '-2');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power('-inf'::numeric, '-3');
+ power
+-------
+ 0
+(1 row)
+
+SELECT power('-inf'::numeric, '0');
+ power
+-------
+ 1
+(1 row)
+
+SELECT power('-inf'::numeric, 'inf');
+ power
+----------
+ Infinity
+(1 row)
+
+SELECT power('-inf'::numeric, '-inf');
+ power
+-------
+ 0
+(1 row)
+
-- ******************************
-- * miscellaneous checks for things that have been broken in the past...
-- ******************************
DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1.
INSERT INTO fract_only VALUES (7, '0.00001');
INSERT INTO fract_only VALUES (8, '0.00017');
+INSERT INTO fract_only VALUES (9, 'NaN');
+INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail
+ERROR: numeric field overflow
+DETAIL: A field with precision 4, scale 4 cannot hold an infinite value.
+INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail
+ERROR: numeric field overflow
+DETAIL: A field with precision 4, scale 4 cannot hold an infinite value.
SELECT * FROM fract_only;
id | val
----+---------
5 | 0.9999
7 | 0.0000
8 | 0.0002
-(6 rows)
+ 9 | NaN
+(7 rows)
DROP TABLE fract_only;
-- Check inf/nan conversion behavior
(1 row)
SELECT 'Infinity'::float8::numeric;
-ERROR: cannot convert infinity to numeric
+ numeric
+----------
+ Infinity
+(1 row)
+
SELECT '-Infinity'::float8::numeric;
-ERROR: cannot convert infinity to numeric
+ numeric
+-----------
+ -Infinity
+(1 row)
+
+SELECT 'NaN'::numeric::float8;
+ float8
+--------
+ NaN
+(1 row)
+
+SELECT 'Infinity'::numeric::float8;
+ float8
+----------
+ Infinity
+(1 row)
+
+SELECT '-Infinity'::numeric::float8;
+ float8
+-----------
+ -Infinity
+(1 row)
+
SELECT 'NaN'::float4::numeric;
numeric
---------
(1 row)
SELECT 'Infinity'::float4::numeric;
-ERROR: cannot convert infinity to numeric
+ numeric
+----------
+ Infinity
+(1 row)
+
SELECT '-Infinity'::float4::numeric;
-ERROR: cannot convert infinity to numeric
+ numeric
+-----------
+ -Infinity
+(1 row)
+
+SELECT 'NaN'::numeric::float4;
+ float4
+--------
+ NaN
+(1 row)
+
+SELECT 'Infinity'::numeric::float4;
+ float4
+----------
+ Infinity
+(1 row)
+
+SELECT '-Infinity'::numeric::float4;
+ float4
+-----------
+ -Infinity
+(1 row)
+
+SELECT '42'::int2::numeric;
+ numeric
+---------
+ 42
+(1 row)
+
+SELECT 'NaN'::numeric::int2;
+ERROR: cannot convert NaN to smallint
+SELECT 'Infinity'::numeric::int2;
+ERROR: cannot convert infinity to smallint
+SELECT '-Infinity'::numeric::int2;
+ERROR: cannot convert infinity to smallint
+SELECT 'NaN'::numeric::int4;
+ERROR: cannot convert NaN to integer
+SELECT 'Infinity'::numeric::int4;
+ERROR: cannot convert infinity to integer
+SELECT '-Infinity'::numeric::int4;
+ERROR: cannot convert infinity to integer
+SELECT 'NaN'::numeric::int8;
+ERROR: cannot convert NaN to bigint
+SELECT 'Infinity'::numeric::int8;
+ERROR: cannot convert infinity to bigint
+SELECT '-Infinity'::numeric::int8;
+ERROR: cannot convert infinity to bigint
-- Simple check that ceil(), floor(), and round() work correctly
CREATE TABLE ceil_floor_round (a numeric);
INSERT INTO ceil_floor_round VALUES ('-5.5');
ERROR: operand, lower bound, and upper bound cannot be NaN
SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888);
ERROR: operand, lower bound, and upper bound cannot be NaN
+SELECT width_bucket('inf', 3.0, 4.0, 888);
+ERROR: operand, lower bound, and upper bound cannot be infinity
+SELECT width_bucket(2.0, 3.0, '-inf', 888);
+ERROR: operand, lower bound, and upper bound cannot be infinity
+SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888);
+ERROR: lower and upper bounds must be finite
-- normal operation
CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8);
COPY width_bucket_test (operand_num) FROM stdin;
| -2.493e+07
(10 rows)
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, '9.999EEEE') as numeric,
+ to_char(val::float8, '9.999EEEE') as float8,
+ to_char(val::float4, '9.999EEEE') as float4
+FROM v;
+ val | numeric | float8 | float4
+------------+------------+------------+------------
+ 0 | 0.000e+00 | 0.000e+00 | 0.000e+00
+ -4.2 | -4.200e+00 | -4.200e+00 | -4.200e+00
+ 4200000000 | 4.200e+09 | 4.200e+09 | 4.200e+09
+ 0.000012 | 1.200e-05 | 1.200e-05 | 1.200e-05
+ Infinity | #.####### | #.####### | #.#######
+ -Infinity | #.####### | #.####### | #.#######
+ NaN | #.####### | #.####### | #.#######
+(7 rows)
+
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, 'MI9999999999.99') as numeric,
+ to_char(val::float8, 'MI9999999999.99') as float8,
+ to_char(val::float4, 'MI9999999999.99') as float4
+FROM v;
+ val | numeric | float8 | float4
+------------+----------------+----------------+----------------
+ 0 | .00 | .00 | .00
+ -4.2 | - 4.20 | - 4.20 | - 4.20
+ 4200000000 | 4200000000.00 | 4200000000.00 | 4200000000
+ 0.000012 | .00 | .00 | .00
+ Infinity | Infinity | Infinity | Infinity
+ -Infinity | - Infinity | - Infinity | - Infinity
+ NaN | NaN | NaN | NaN
+(7 rows)
+
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, 'MI99.99') as numeric,
+ to_char(val::float8, 'MI99.99') as float8,
+ to_char(val::float4, 'MI99.99') as float4
+FROM v;
+ val | numeric | float8 | float4
+------------+---------+--------+--------
+ 0 | .00 | .00 | .00
+ -4.2 | - 4.20 | - 4.20 | - 4.20
+ 4200000000 | ##.## | ##.## | ##.
+ 0.000012 | .00 | .00 | .00
+ Infinity | ##.## | ##.## | ##.
+ -Infinity | -##.## | -##.## | -##.
+ NaN | ##.## | ##.## | ##.##
+(7 rows)
+
SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9');
to_char_24 | to_char
------------+---------
INSERT INTO num_input_test(n1) VALUES ('-555.50');
INSERT INTO num_input_test(n1) VALUES ('NaN ');
INSERT INTO num_input_test(n1) VALUES (' nan');
+INSERT INTO num_input_test(n1) VALUES (' inf ');
+INSERT INTO num_input_test(n1) VALUES (' +inf ');
+INSERT INTO num_input_test(n1) VALUES (' -inf ');
+INSERT INTO num_input_test(n1) VALUES (' Infinity ');
+INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
+INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
-- bad inputs
INSERT INTO num_input_test(n1) VALUES (' ');
ERROR: invalid input syntax for type numeric: " "
ERROR: invalid input syntax for type numeric: " N aN "
LINE 1: INSERT INTO num_input_test(n1) VALUES (' N aN ');
^
+INSERT INTO num_input_test(n1) VALUES ('+ infinity');
+ERROR: invalid input syntax for type numeric: "+ infinity"
+LINE 1: INSERT INTO num_input_test(n1) VALUES ('+ infinity');
+ ^
SELECT * FROM num_input_test;
- n1
----------
- 123
- 3245874
- -93853
- 555.50
- -555.50
- NaN
- NaN
-(7 rows)
+ n1
+-----------
+ 123
+ 3245874
+ -93853
+ 555.50
+ -555.50
+ NaN
+ NaN
+ Infinity
+ Infinity
+ -Infinity
+ Infinity
+ Infinity
+ -Infinity
+(13 rows)
--
-- Test some corner cases for multiplication
2.7182818284590452353602874713526624977572470936999595749669676277240766
(1 row)
+select exp('nan'::numeric);
+ exp
+-----
+ NaN
+(1 row)
+
+select exp('inf'::numeric);
+ exp
+----------
+ Infinity
+(1 row)
+
+select exp('-inf'::numeric);
+ exp
+-----
+ 0
+(1 row)
+
-- cases that used to generate inaccurate results
select exp(32.999);
exp
ERROR: start value cannot be NaN
select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric);
ERROR: stop value cannot be NaN
+select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric);
+ERROR: start value cannot be infinity
+select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric);
+ERROR: stop value cannot be infinity
+select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric);
+ERROR: step size cannot be infinity
-- Checks maximum, output is truncated
select (i / (10::numeric ^ 131071))::numeric(1,0)
from generate_series(6 * (10::numeric ^ 131071),
(1 row)
+select scale(numeric 'inf');
+ scale
+-------
+
+(1 row)
+
select scale(NULL::numeric);
scale
-------
t
(1 row)
+select min_scale(numeric 'inf') is NULL; -- should be true
+ ?column?
+----------
+ t
+(1 row)
+
select min_scale(0); -- no digits
min_scale
-----------
NaN
(1 row)
+select trim_scale(numeric 'inf');
+ trim_scale
+------------
+ Infinity
+(1 row)
+
select trim_scale(1.120);
trim_scale
------------
(0::numeric, 46375::numeric),
(433125::numeric, 46375::numeric),
(43312.5::numeric, 4637.5::numeric),
- (4331.250::numeric, 463.75000::numeric)) AS v(a, b);
+ (4331.250::numeric, 463.75000::numeric),
+ ('inf', '0'),
+ ('inf', '42'),
+ ('inf', 'inf')
+ ) AS v(a, b);
a | b | gcd | gcd | gcd | gcd
----------+-----------+---------+---------+---------+---------
0 | 0 | 0 | 0 | 0 | 0
433125 | 46375 | 875 | 875 | 875 | 875
43312.5 | 4637.5 | 87.5 | 87.5 | 87.5 | 87.5
4331.250 | 463.75000 | 8.75000 | 8.75000 | 8.75000 | 8.75000
-(6 rows)
+ Infinity | 0 | NaN | NaN | NaN | NaN
+ Infinity | 42 | NaN | NaN | NaN | NaN
+ Infinity | Infinity | NaN | NaN | NaN | NaN
+(9 rows)
--
-- Tests for LCM()
(13272::numeric, 13272::numeric),
(423282::numeric, 13272::numeric),
(42328.2::numeric, 1327.2::numeric),
- (4232.820::numeric, 132.72000::numeric)) AS v(a, b);
+ (4232.820::numeric, 132.72000::numeric),
+ ('inf', '0'),
+ ('inf', '42'),
+ ('inf', 'inf')
+ ) AS v(a, b);
a | b | lcm | lcm | lcm | lcm
----------+-----------+--------------+--------------+--------------+--------------
0 | 0 | 0 | 0 | 0 | 0
423282 | 13272 | 11851896 | 11851896 | 11851896 | 11851896
42328.2 | 1327.2 | 1185189.6 | 1185189.6 | 1185189.6 | 1185189.6
4232.820 | 132.72000 | 118518.96000 | 118518.96000 | 118518.96000 | 118518.96000
-(7 rows)
+ Infinity | 0 | NaN | NaN | NaN | NaN
+ Infinity | 42 | NaN | NaN | NaN | NaN
+ Infinity | Infinity | NaN | NaN | NaN | NaN
+(10 rows)
SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow
ERROR: value overflows numeric format
WHERE t1.id1 = t2.id
AND t1.result != t2.expected;
+-- ******************************
+-- * Check behavior with Inf and NaN inputs. It's easiest to handle these
+-- * separately from the num_data framework used above, because some input
+-- * combinations will throw errors.
+-- ******************************
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
+SELECT x1, x2,
+ x1 + x2 AS sum,
+ x1 - x2 AS diff,
+ x1 * x2 AS prod
+FROM v AS v1(x1), v AS v2(x2);
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('inf'),('-inf'),('nan'))
+SELECT x1, x2,
+ x1 / x2 AS quot,
+ x1 % x2 AS mod,
+ div(x1, x2) AS div
+FROM v AS v1(x1), v AS v2(x2) WHERE x2 != 0;
+
+SELECT 'inf'::numeric / '0';
+SELECT '-inf'::numeric / '0';
+SELECT 'nan'::numeric / '0';
+SELECT '0'::numeric / '0';
+SELECT 'inf'::numeric % '0';
+SELECT '-inf'::numeric % '0';
+SELECT 'nan'::numeric % '0';
+SELECT '0'::numeric % '0';
+SELECT div('inf'::numeric, '0');
+SELECT div('-inf'::numeric, '0');
+SELECT div('nan'::numeric, '0');
+SELECT div('0'::numeric, '0');
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
+SELECT x, -x as minusx, abs(x), floor(x), ceil(x), sign(x), numeric_inc(x) as inc
+FROM v;
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('inf'),('-inf'),('nan'))
+SELECT x, round(x), round(x,1) as round1, trunc(x), trunc(x,1) as trunc1
+FROM v;
+
+-- the large values fall into the numeric abbreviation code's maximal classes
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('-1'),('4.2'),('-7.777'),('1e340'),('-1e340'),
+ ('inf'),('-inf'),('nan'),
+ ('inf'),('-inf'),('nan'))
+SELECT substring(x::text, 1, 32)
+FROM v ORDER BY x;
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('4.2'),('inf'),('nan'))
+SELECT x, sqrt(x)
+FROM v;
+
+SELECT sqrt('-1'::numeric);
+SELECT sqrt('-inf'::numeric);
+
+WITH v(x) AS
+ (VALUES('1'::numeric),('4.2'),('inf'),('nan'))
+SELECT x,
+ log(x),
+ log10(x),
+ ln(x)
+FROM v;
+
+SELECT ln('0'::numeric);
+SELECT ln('-1'::numeric);
+SELECT ln('-inf'::numeric);
+
+WITH v(x) AS
+ (VALUES('2'::numeric),('4.2'),('inf'),('nan'))
+SELECT x1, x2,
+ log(x1, x2)
+FROM v AS v1(x1), v AS v2(x2);
+
+SELECT log('0'::numeric, '10');
+SELECT log('10'::numeric, '0');
+SELECT log('-inf'::numeric, '10');
+SELECT log('10'::numeric, '-inf');
+SELECT log('inf'::numeric, '0');
+SELECT log('inf'::numeric, '-inf');
+SELECT log('-inf'::numeric, 'inf');
+
+WITH v(x) AS
+ (VALUES('0'::numeric),('1'),('2'),('4.2'),('inf'),('nan'))
+SELECT x1, x2,
+ power(x1, x2)
+FROM v AS v1(x1), v AS v2(x2) WHERE x1 != 0 OR x2 >= 0;
+
+SELECT power('0'::numeric, '-1');
+SELECT power('0'::numeric, '-inf');
+SELECT power('-1'::numeric, 'inf');
+SELECT power('-2'::numeric, '3');
+SELECT power('-2'::numeric, '3.3');
+SELECT power('-2'::numeric, '-1');
+SELECT power('-2'::numeric, '-1.5');
+SELECT power('-2'::numeric, 'inf');
+SELECT power('-2'::numeric, '-inf');
+SELECT power('inf'::numeric, '-2');
+SELECT power('inf'::numeric, '-inf');
+SELECT power('-inf'::numeric, '2');
+SELECT power('-inf'::numeric, '3');
+SELECT power('-inf'::numeric, '4.5');
+SELECT power('-inf'::numeric, '-2');
+SELECT power('-inf'::numeric, '-3');
+SELECT power('-inf'::numeric, '0');
+SELECT power('-inf'::numeric, 'inf');
+SELECT power('-inf'::numeric, '-inf');
+
-- ******************************
-- * miscellaneous checks for things that have been broken in the past...
-- ******************************
INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail
INSERT INTO fract_only VALUES (7, '0.00001');
INSERT INTO fract_only VALUES (8, '0.00017');
+INSERT INTO fract_only VALUES (9, 'NaN');
+INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail
+INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail
SELECT * FROM fract_only;
DROP TABLE fract_only;
SELECT 'NaN'::float8::numeric;
SELECT 'Infinity'::float8::numeric;
SELECT '-Infinity'::float8::numeric;
+SELECT 'NaN'::numeric::float8;
+SELECT 'Infinity'::numeric::float8;
+SELECT '-Infinity'::numeric::float8;
SELECT 'NaN'::float4::numeric;
SELECT 'Infinity'::float4::numeric;
SELECT '-Infinity'::float4::numeric;
+SELECT 'NaN'::numeric::float4;
+SELECT 'Infinity'::numeric::float4;
+SELECT '-Infinity'::numeric::float4;
+SELECT '42'::int2::numeric;
+SELECT 'NaN'::numeric::int2;
+SELECT 'Infinity'::numeric::int2;
+SELECT '-Infinity'::numeric::int2;
+SELECT 'NaN'::numeric::int4;
+SELECT 'Infinity'::numeric::int4;
+SELECT '-Infinity'::numeric::int4;
+SELECT 'NaN'::numeric::int8;
+SELECT 'Infinity'::numeric::int8;
+SELECT '-Infinity'::numeric::int8;
-- Simple check that ceil(), floor(), and round() work correctly
CREATE TABLE ceil_floor_round (a numeric);
SELECT width_bucket(3.5::float8, 3.0::float8, 3.0::float8, 888);
SELECT width_bucket('NaN', 3.0, 4.0, 888);
SELECT width_bucket(0::float8, 'NaN', 4.0::float8, 888);
+SELECT width_bucket('inf', 3.0, 4.0, 888);
+SELECT width_bucket(2.0, 3.0, '-inf', 888);
+SELECT width_bucket(0::float8, '-inf', 4.0::float8, 888);
-- normal operation
CREATE TABLE width_bucket_test (operand_num numeric, operand_f8 float8);
SELECT '' AS to_char_22, to_char(val, 'FM9999999999999999.999999999999999') FROM num_data;
SELECT '' AS to_char_23, to_char(val, '9.999EEEE') FROM num_data;
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, '9.999EEEE') as numeric,
+ to_char(val::float8, '9.999EEEE') as float8,
+ to_char(val::float4, '9.999EEEE') as float4
+FROM v;
+
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, 'MI9999999999.99') as numeric,
+ to_char(val::float8, 'MI9999999999.99') as float8,
+ to_char(val::float4, 'MI9999999999.99') as float4
+FROM v;
+
+WITH v(val) AS
+ (VALUES('0'::numeric),('-4.2'),('4.2e9'),('1.2e-5'),('inf'),('-inf'),('nan'))
+SELECT val,
+ to_char(val, 'MI99.99') as numeric,
+ to_char(val::float8, 'MI99.99') as float8,
+ to_char(val::float4, 'MI99.99') as float4
+FROM v;
+
SELECT '' AS to_char_24, to_char('100'::numeric, 'FM999.9');
SELECT '' AS to_char_25, to_char('100'::numeric, 'FM999.');
SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999');
INSERT INTO num_input_test(n1) VALUES ('-555.50');
INSERT INTO num_input_test(n1) VALUES ('NaN ');
INSERT INTO num_input_test(n1) VALUES (' nan');
+INSERT INTO num_input_test(n1) VALUES (' inf ');
+INSERT INTO num_input_test(n1) VALUES (' +inf ');
+INSERT INTO num_input_test(n1) VALUES (' -inf ');
+INSERT INTO num_input_test(n1) VALUES (' Infinity ');
+INSERT INTO num_input_test(n1) VALUES (' +inFinity ');
+INSERT INTO num_input_test(n1) VALUES (' -INFINITY ');
-- bad inputs
INSERT INTO num_input_test(n1) VALUES (' ');
INSERT INTO num_input_test(n1) VALUES ('5. 0 ');
INSERT INTO num_input_test(n1) VALUES ('');
INSERT INTO num_input_test(n1) VALUES (' N aN ');
+INSERT INTO num_input_test(n1) VALUES ('+ infinity');
SELECT * FROM num_input_test;
select exp(0.0);
select exp(1.0);
select exp(1.0::numeric(71,70));
+select exp('nan'::numeric);
+select exp('inf'::numeric);
+select exp('-inf'::numeric);
-- cases that used to generate inaccurate results
select exp(32.999);
select * from generate_series(-100::numeric, 100::numeric, 'nan'::numeric);
select * from generate_series('nan'::numeric, 100::numeric, 10::numeric);
select * from generate_series(0::numeric, 'nan'::numeric, 10::numeric);
+select * from generate_series('inf'::numeric, 'inf'::numeric, 10::numeric);
+select * from generate_series(0::numeric, 'inf'::numeric, 10::numeric);
+select * from generate_series(0::numeric, '42'::numeric, '-inf'::numeric);
-- Checks maximum, output is truncated
select (i / (10::numeric ^ 131071))::numeric(1,0)
from generate_series(6 * (10::numeric ^ 131071),
--
select scale(numeric 'NaN');
+select scale(numeric 'inf');
select scale(NULL::numeric);
select scale(1.12);
select scale(0);
--
select min_scale(numeric 'NaN') is NULL; -- should be true
+select min_scale(numeric 'inf') is NULL; -- should be true
select min_scale(0); -- no digits
select min_scale(0.00); -- no digits again
select min_scale(1.0); -- no scale
--
select trim_scale(numeric 'NaN');
+select trim_scale(numeric 'inf');
select trim_scale(1.120);
select trim_scale(0);
select trim_scale(0.00);
(0::numeric, 46375::numeric),
(433125::numeric, 46375::numeric),
(43312.5::numeric, 4637.5::numeric),
- (4331.250::numeric, 463.75000::numeric)) AS v(a, b);
+ (4331.250::numeric, 463.75000::numeric),
+ ('inf', '0'),
+ ('inf', '42'),
+ ('inf', 'inf')
+ ) AS v(a, b);
--
-- Tests for LCM()
(13272::numeric, 13272::numeric),
(423282::numeric, 13272::numeric),
(42328.2::numeric, 1327.2::numeric),
- (4232.820::numeric, 132.72000::numeric)) AS v(a, b);
+ (4232.820::numeric, 132.72000::numeric),
+ ('inf', '0'),
+ ('inf', '42'),
+ ('inf', 'inf')
+ ) AS v(a, b);
SELECT lcm(9999 * (10::numeric)^131068 + (10::numeric^131068 - 1), 2); -- overflow