FluentValidation/FluentValidation · error · ArgumentOutOfRangeException

Scale must be a positive integer. [value:{Scale}].

Error message

Scale must be a positive integer. [value:{Scale}].

What it means

Constructor precondition of PrecisionScaleValidator: the scale (number of digits after the decimal point) must not be negative. Thrown immediately when PrecisionScale(precision, scale, ignoreTrailingZeros) is called with a negative scale. Note the message says 'positive integer' but the guard only rejects values below zero, so 0 is accepted.

Source

Thrown at src/FluentValidation/Validators/PrecisionScaleValidator.cs:50

///
/// It implies certain range of values that will be accepted by the validator.
/// It permits up to Precision - Scale digits to the left of the decimal point and up to Scale digits to the right.
///
/// It can be configured to use the effective scale and precision
/// (i.e. ignore trailing zeros) if required.
///
/// 123.4500 has an scale of 4 and a precision of 7, but an effective scale
/// and precision of 2 and 5 respectively.
/// </summary>
public class PrecisionScaleValidator<T> : PropertyValidator<T, decimal> {

	public PrecisionScaleValidator(int precision, int scale, bool ignoreTrailingZeros) {
		Scale = scale;
		Precision = precision;
		IgnoreTrailingZeros = ignoreTrailingZeros;

		if (Scale < 0)
			throw new ArgumentOutOfRangeException(nameof(scale), $"Scale must be a positive integer. [value:{Scale}].");

		if (Precision < 0)
			throw new ArgumentOutOfRangeException(nameof(precision), $"Precision must be a positive integer. [value:{Precision}].");

		if (Precision < Scale)
			throw new ArgumentOutOfRangeException(nameof(scale), $"Scale must be less than precision. [scale:{Scale}, precision:{Precision}].");
	}

	public override string Name => "ScalePrecisionValidator";

	public int Scale { get; }

	public int Precision { get; }

	public bool IgnoreTrailingZeros { get; }

	public override bool IsValid(ValidationContext<T> context, decimal decimalValue) {
		var info = Info.Get(decimalValue, IgnoreTrailingZeros);

View on GitHub (pinned to daa00b7954)

Solutions

  1. Pass a non-negative scale as the second argument, e.g. .PrecisionScale(5, 2, false) for up to 5 total digits with 2 after the decimal.
  2. Remember argument order is (precision, scale, ignoreTrailingZeros) — scale is second.
  3. If scale comes from config, validate/clamp it to >= 0 before building the rule.

Example fix

// before
RuleFor(x => x.Amount).PrecisionScale(5, -2, false);

// after
RuleFor(x => x.Amount).PrecisionScale(5, 2, false);
Defensive patterns

Strategy: validation

Validate before calling

int scale = 2;
if (scale < 0) throw new ArgumentOutOfRangeException(nameof(scale));
RuleFor(x => x.Amount).PrecisionScale(5, scale, false);

Prevention

When it happens

Trigger: Calling RuleFor(x => x.Amount).PrecisionScale(precision, scale, ignoreTrailingZeros) with scale < 0, or direct construction new PrecisionScaleValidator<T>(5, -1, false).

Common situations: Negative scale passed due to a config typo or a computed value that underflows. Misunderstanding scale vs precision and passing the wrong argument. Argument-order confusion (precision comes first).

Related errors


AI-assisted analysis of FluentValidation/FluentValidation@daa00b7954 (2026-08-13). Data as JSON: /api/errors/8aadfbe3d30f49b1. Report an issue: GitHub.