diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 6abda2f1d2..d3c70667a3 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -545,8 +545,8 @@ NUMERIC(precision, scale) - The precision must be positive, the scale may be positive or negative - (see below). Alternatively: + The precision must be positive, while the scale may be positive or + negative (see below). Alternatively: NUMERIC(precision) @@ -569,8 +569,8 @@ NUMERIC The maximum precision that can be explicitly specified in - a NUMERIC type declaration is 1000. An - unconstrained NUMERIC column is subject to the limits + a numeric type declaration is 1000. An + unconstrained numeric column is subject to the limits described in . @@ -578,38 +578,48 @@ NUMERIC If the scale of a value to be stored is greater than the declared scale of the column, the system will round the value to the specified - number of fractional digits. If the declared scale of the column is - negative, the value will be rounded to the left of the decimal point. - If, after rounding, the number of digits to the left of the decimal point - exceeds the declared precision minus the declared scale, an error is - raised. Similarly, if the declared scale exceeds the declared precision - and the number of zero digits to the right of the decimal point is less - than the declared scale minus the declared precision, an error is raised. + number of fractional digits. Then, if the number of digits to the + left of the decimal point exceeds the declared precision minus the + declared scale, an error is raised. For example, a column declared as NUMERIC(3, 1) - will round values to 1 decimal place and be able to store values between - -99.9 and 99.9, inclusive. A column declared as + will round values to 1 decimal place and can store values between + -99.9 and 99.9, inclusive. + + + + Beginning in PostgreSQL 15, it is allowed + to declare a numeric column with a negative scale. Then + values will be rounded to the left of the decimal point. The + precision still represents the maximum number of non-rounded digits. + Thus, a column declared as NUMERIC(2, -3) - will round values to the nearest thousand and be able to store values - between -99000 and 99000, inclusive. A column declared as + will round values to the nearest thousand and can store values + between -99000 and 99000, inclusive. + It is also allowed to declare a scale larger than the declared + precision. Such a column can only hold fractional values, and it + requires the number of zero digits just to the right of the decimal + point to be at least the declared scale minus the declared precision. + For example, a column declared as NUMERIC(3, 5) - will round values to 5 decimal places and be able to store values between + will round values to 5 decimal places and can store values between -0.00999 and 0.00999, inclusive. - The scale in a NUMERIC type declaration may be any value in - the range -1000 to 1000. (The SQL standard requires - the scale to be in the range 0 to precision. - Using values outside this range may not be portable to other database - systems.) + PostgreSQL permits the scale in + a numeric type declaration to be any value in the range + -1000 to 1000. However, the SQL standard requires + the scale to be in the range 0 + to precision. Using scales outside that + range may not be portable to other database systems. diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c index 46cb37cea1..faff09f5d5 100644 --- a/src/backend/utils/adt/numeric.c +++ b/src/backend/utils/adt/numeric.c @@ -827,21 +827,31 @@ numeric_is_integral(Numeric num) * * For purely historical reasons VARHDRSZ is then added to the result, thus * the unused space in the upper 16 bits is not all as freely available as it - * might seem. + * might seem. (We can't let the result overflow to a negative int32, as + * other parts of the system would interpret that as not-a-valid-typmod.) */ -static inline int +static inline int32 make_numeric_typmod(int precision, int scale) { return ((precision << 16) | (scale & 0x7ff)) + VARHDRSZ; } +/* + * Because of the offset, valid numeric typmods are at least VARHDRSZ + */ +static inline bool +is_valid_numeric_typmod(int32 typmod) +{ + return typmod >= (int32) VARHDRSZ; +} + /* * numeric_typmod_precision() - * * Extract the precision from a numeric typmod --- see make_numeric_typmod(). */ static inline int -numeric_typmod_precision(int typmod) +numeric_typmod_precision(int32 typmod) { return ((typmod - VARHDRSZ) >> 16) & 0xffff; } @@ -856,7 +866,7 @@ numeric_typmod_precision(int typmod) * extends an 11-bit two's complement number x. */ static inline int -numeric_typmod_scale(int typmod) +numeric_typmod_scale(int32 typmod) { return (((typmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024; } @@ -872,7 +882,7 @@ numeric_maximum_size(int32 typmod) int precision; int numeric_digits; - if (typmod < (int32) (VARHDRSZ)) + if (!is_valid_numeric_typmod(typmod)) return -1; /* precision (ie, max # of digits) is in upper bits of typmod */ @@ -1136,14 +1146,14 @@ numeric_support(PG_FUNCTION_ARGS) int32 new_precision = numeric_typmod_precision(new_typmod); /* - * If new_typmod < VARHDRSZ, the destination is unconstrained; - * that's always OK. If old_typmod >= VARHDRSZ, the source is + * If new_typmod is invalid, the destination is unconstrained; + * that's always OK. If old_typmod is valid, the source is * constrained, and we're OK if the scale is unchanged and the * precision is not decreasing. See further notes in function * header comment. */ - if (new_typmod < (int32) VARHDRSZ || - (old_typmod >= (int32) VARHDRSZ && + if (!is_valid_numeric_typmod(new_typmod) || + (is_valid_numeric_typmod(old_typmod) && new_scale == old_scale && new_precision >= old_precision)) ret = relabel_to_typmod(source, new_typmod); } @@ -1186,7 +1196,7 @@ numeric (PG_FUNCTION_ARGS) * If the value isn't a valid type modifier, simply return a copy of the * input value */ - if (typmod < (int32) (VARHDRSZ)) + if (!is_valid_numeric_typmod(typmod)) PG_RETURN_NUMERIC(duplicate_numeric(num)); /* @@ -1288,7 +1298,7 @@ numerictypmodout(PG_FUNCTION_ARGS) int32 typmod = PG_GETARG_INT32(0); char *res = (char *) palloc(64); - if (typmod >= 0) + if (is_valid_numeric_typmod(typmod)) snprintf(res, 64, "(%d,%d)", numeric_typmod_precision(typmod), numeric_typmod_scale(typmod)); @@ -7476,8 +7486,8 @@ apply_typmod(NumericVar *var, int32 typmod) int ddigits; int i; - /* Do nothing if we have a default typmod (-1) */ - if (typmod < (int32) (VARHDRSZ)) + /* Do nothing if we have an invalid typmod */ + if (!is_valid_numeric_typmod(typmod)) return; precision = numeric_typmod_precision(typmod); @@ -7565,7 +7575,7 @@ apply_typmod_special(Numeric num, int32 typmod) return; /* Do nothing if we have a default typmod (-1) */ - if (typmod < (int32) (VARHDRSZ)) + if (!is_valid_numeric_typmod(typmod)) return; precision = numeric_typmod_precision(typmod);