You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何精准测试存在小数精度差异的食材营养计算函数?

如何精准测试存在小数精度问题的宏量营养素计算函数?

我开发了一款可调整食材用量并同步计算宏量营养素的函数——比如把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 16:35:50