问题描述
- 精度丢失:在数据库里,
float
和double
类型以二进制近似存储小数,会引发精度丢失。比如计算0.1 * 3
时,结果可能是0.30000000000000004
,在商品价格计算等对精度要求高的场景下不可接受。 - 类型转换:使用
decimal
类型存储小数,从数据库取出的数据默认是字符串类型,在代码里需要手动转换为number
类型,否则会影响后续计算和业务逻辑。
解决方案
1. 使用 decimal
类型配合 transformer
decimal
类型以十进制形式存储数据,能精确表示和计算小数。借助 transformer
可在数据存入数据库和从数据库取出时自动转换类型。
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ValueTransformer } from 'typeorm';
// 定义转换器
const decimalTransformer: ValueTransformer = {
to: (value: number) => value, // 存入数据库时,直接使用原始值
from: (value: string) => parseFloat(value), // 从数据库取出时,将字符串转换为 number 类型
};
@Entity()
export class CouponBatch {
@PrimaryGeneratedColumn()
id: number;
// ... 已有代码 ...
@Column({ type: 'decimal', name: 'value', nullable: false,
precision: 10, // 总位数
scale: 2, // 小数位数
transformer: decimalTransformer
})
value: number;
@Column({ type: 'decimal', name: 'min_price', nullable: false,
precision: 10, scale: 2, transformer: decimalTransformer
})
minPrice: number;
// ... 已有代码 ...
}
2. 说明
precision
:指定decimal
类型的总位数,即整数部分和小数部分的位数之和。scale
:指定小数部分的位数。transformer
:包含to
和from
两个方法,to
方法在数据存入数据库时调用,from
方法在从数据库取出数据时调用。
DTO验证,使用自定义装饰器
//文件目录src/utils/validators/is-two-decimal.validator
import { applyDecorators } from '@nestjs/common';
import { IsNumber, ValidateBy, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
// 定义验证约束
@ValidatorConstraint({ name: 'isTwoDecimal', async: false })
export class IsTwoDecimal implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
// 检查值是否为数字且小数位数最多为两位
if (typeof value !== 'number') {
return false;
}
const decimalPart = value.toString().split('.')[1];
return !decimalPart || decimalPart.length <= 2;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} 必须为最多两位小数`;
}
}
// 自定义装饰器工厂函数
export function IsTwoDecimalDecorator(validationOptions?: ValidationOptions) {
const defaultValidationOptions = {
...validationOptions,
// 合并默认错误消息,如果用户没有提供自定义消息,则使用默认消息
message: validationOptions?.message || (args => new IsTwoDecimal().defaultMessage(args)),
};
return applyDecorators(
IsNumber({}, defaultValidationOptions),
ValidateBy(
{
name: 'isTwoDecimal',
// 使用 IsTwoDecimal 实例作为验证器
validator: new IsTwoDecimal(),
},
defaultValidationOptions,
),
);
}
DTO中使用装饰器
import { IsTwoDecimalDecorator } from 'src/utils/validators/is-two-decimal.validator';
@IsTwoDecimalDecorator({ message: '最低价最多两位小数' })
minPrice: number;
总结
- 若对精度要求高,推荐使用
decimal
类型配合transformer
,能保证数据的精确存储和计算,同时在代码里方便处理number
类型数据。 - 若对精度要求不高,可使用
float
或double
类型,但要注意可能出现的精度丢失问题。