- Published on
Validate Numbers with Up to 2 Decimal Places Using Zod
- Authors
- Name
- Ripal & Zalak
Validating Numbers with Two Decimal Places Using Zod
In many TypeScript or JavaScript projects, you might need to ensure a number has up to two decimal places. For example:
- ✅
1
,1.1
,1.11
- ❌
1.111
,1.12345
Here’s how you can achieve this using the popular Zod validation library.
Problem
By default, Zod provides a powerful schema validation mechanism, but ensuring precision in decimal places can be tricky. A naive solution is to use z.number().multipleOf(0.01)
:
import { z } from 'zod'
const schema = z.number().multipleOf(0.01)
schema.parse(1.11) // ✅ Passes
schema.parse(1.111) // ❌ Throws error
This works in most cases but may fail due to floating-point precision issues. For instance:
schema.parse(0.1 + 0.1 + 0.1) // ❌ Fails
The reason? 0.1 + 0.1 + 0.1
becomes 0.30000000000000004
internally.
refine()
A Better Approach Using To reliably validate numbers with up to two decimal places, use Zod's refine()
method. This method allows custom validation logic, such as handling floating-point quirks with Number.EPSILON
:
import { z } from 'zod'
const twoDecimalSchema = z.number().refine(
(value) => {
const multiplier = 100
const fractionalPart = value * multiplier - Math.trunc(value * multiplier)
return Math.abs(fractionalPart) < Number.EPSILON
},
{
message: 'Number must have up to two decimal places',
}
)
// Usage
twoDecimalSchema.parse(1.11) // ✅ Passes
twoDecimalSchema.parse(1.111) // ❌ Throws error
This approach accounts for floating-point precision issues by comparing against Number.EPSILON
.
Explanation
Why
Number.EPSILON
?- Floating-point arithmetic in JavaScript can produce small errors.
Number.EPSILON
provides a threshold to check for approximate equality.
- Floating-point arithmetic in JavaScript can produce small errors.
How does it work?
- Multiply the number by 100.
- Subtract its truncated value (
Math.trunc
). - Validate that the result is within the small threshold of
Number.EPSILON
.
Why
refine()
overcustom()
?- Both are valid options. However,
refine()
is concise, easier to use, and integrates seamlessly with error messages.
- Both are valid options. However,
FAQs
Q: Can I validate decimal places for strings instead?
Yes! If your input is a string, you can use z.string().regex()
:
const stringSchema = z.string().regex(/^\d+(\.\d{1,2})?$/, {
message: 'Must have up to 2 decimal places',
})
Q: What happens if the input has more than two decimal places?
The schema will throw a validation error with the custom message you defined.
Q: Why not use multipleOf(0.01)
?
multipleOf(0.01)
works but may fail for numbers generated by operations like 0.1 + 0.1 + 0.1
. Use refine()
for higher reliability.