如何精准测试存在小数精度差异的食材营养计算函数?
如何精准测试存在小数精度问题的宏量营养素计算函数?
我开发了一款可调整食材用量并同步计算宏量营养素的函数——比如把100g大米调整为150g时,会将其宏量营养素乘以1.5。以下是我的测试代码:
const fakeIngredient = { product_name: 'test', number_of_servings: 100, serving_quantity: 1, nutrients: { 'energy-kcal_serving': 100, carbohydrates_serving: 3.75, fat_serving: 1.25, protein_serving: 3.773 }, amount: 100, uid: 'test' }; const fakeIngredientChanged = { product_name: 'test', number_of_servings: 140, serving_quantity: 1, nutrients: { 'energy-kcal_serving': 140, carbohydrates_serving: 5.25, fat_serving: 1.75, protein_serving: 5.2822 }, amount: 140, uid: 'test' }; describe('Home', () => { it('should change amount of ingredient', () => { const result = changeAmountOfIngredient( fakeIngredient.serving_quantity, 140, fakeIngredient ); expect(result).toStrictEqual(fakeIngredientChanged); }); });
测试失败的原因是小数精度问题,实际返回值和预期值存在微小差异:
- "protein_serving": 5.2822, + "protein_serving": 5.2822000000000005,
我手动用计算器计算的结果和函数逻辑一致,但因为JS的小数精度特性导致测试不通过。请问该如何精准测试这个计算函数?我是否存在操作失误?
解决方法
你没有操作失误,这是JavaScript浮点数精度的固有问题——二进制无法精确表示所有十进制小数,计算时会产生微小的舍入误差。针对这个问题,可以用以下几种方式优化测试:
1. 使用精度匹配而非严格相等
不要用toStrictEqual做全对象的严格比对,而是对数值类型的营养素单独设置精度阈值:
describe('Home', () => { it('should change amount of ingredient', () => { const result = changeAmountOfIngredient( fakeIngredient.serving_quantity, 140, fakeIngredient ); // 比对非数值字段的严格相等 expect(result.product_name).toBe(fakeIngredientChanged.product_name); expect(result.amount).toBe(fakeIngredientChanged.amount); // 对营养素数值设置精度匹配 expect(result.nutrients['energy-kcal_serving']).toBeCloseTo(fakeIngredientChanged.nutrients['energy-kcal_serving'], 4); expect(result.nutrients.carbohydrates_serving).toBeCloseTo(fakeIngredientChanged.nutrients.carbohydrates_serving, 4); expect(result.nutrients.fat_serving).toBeCloseTo(fakeIngredientChanged.nutrients.fat_serving, 4); expect(result.nutrients.protein_serving).toBeCloseTo(fakeIngredientChanged.nutrients.protein_serving, 4); }); });
toBeCloseTo的第二个参数是小数点后要匹配的位数,这里设为4就可以覆盖你的场景。
2. 对计算结果进行四舍五入处理
在changeAmountOfIngredient函数内部,计算完成后对营养素数值保留指定小数位(比如4位),这样返回的结果就和手动计算的预期值一致:
function changeAmountOfIngredient(servingQty, newAmount, ingredient) { const ratio = newAmount / ingredient.amount; const updatedNutrients = {}; for (const [key, value] of Object.entries(ingredient.nutrients)) { // 保留4位小数 updatedNutrients[key] = Number((value * ratio).toFixed(4)); } return { ...ingredient, number_of_servings: newAmount, amount: newAmount, nutrients: updatedNutrients }; }
注意toFixed返回的是字符串,需要用Number()转成数值类型。
3. 使用高精度十进制计算库
如果需要更高精度的十进制计算,可以引入专门的库(比如decimal.js),用BigDecimal替代原生浮点数:
import Decimal from 'decimal.js'; function changeAmountOfIngredient(servingQty, newAmount, ingredient) { const ratio = new Decimal(newAmount).div(ingredient.amount); const updatedNutrients = {}; for (const [key, value] of Object.entries(ingredient.nutrients)) { updatedNutrients[key] = new Decimal(value).mul(ratio).toNumber(); } return { ...ingredient, number_of_servings: newAmount, amount: newAmount, nutrients: updatedNutrients }; }
这种方式能从根源避免浮点数精度问题,但需要额外引入依赖。
内容的提问来源于stack exchange,提问作者Ripas55
相关产品推荐
相关产品推荐

