问题描述

  1. 精度丢失:在数据库里,floatdouble 类型以二进制近似存储小数,会引发精度丢失。比如计算 0.1 * 3 时,结果可能是 0.30000000000000004,在商品价格计算等对精度要求高的场景下不可接受。
  2. 类型转换:使用 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:包含 tofrom 两个方法,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 类型数据。
  • 若对精度要求不高,可使用 floatdouble 类型,但要注意可能出现的精度丢失问题。
最后修改:2025 年 05 月 14 日
如果觉得我的文章对你有用,请随意赞赏