跳到正文
DEV Community· Leene Casella·· 4 小時前AI 評分39

腰圍身高比(WHtR)計算器的邊界情況:單位、捨入與界限

Edge Cases in a Waist-to-Height Ratio Calculator: Units, Rounding, and Boundaries

AI 導讀

一個免依賴的 JavaScript 模組及其測試,示範如何處理腰圍身高比計算器的單位轉換、浮點誤差、捨入與輸入驗證。比較結果時以 1e-9 容差處理浮點誤差,最大腰圍則向下捨入;示例界限 0.5 只供程式示範,實際標準應以主管文件為準。

正文

Waist-to-height ratio (WHtR) looks like the easiest calculator you could write: divide one number by another. It's also a good example of how a "trivial" calculator goes wrong in production. Users type centimeters into an inches box. Floating point makes a value that should sit exactly on a limit land a hair above it. A display rounded to two decimals disagrees with the pass/fail check underneath it.

This post walks through a small, dependency-free JavaScript module for WHtR and the tests that keep it honest. The limit in the examples (0.5) is only an illustration for the code. If you build a calculator for a real standard, take the threshold from the governing document and cite it on the page.

The core: one unit internally

The first decision is to convert everything to one unit as soon as input arrives, then do all comparisons in that unit. Mixing units in the middle of the logic is where most bugs start.

const IN_PER_CM = 1 / 2.54;

function toInches(value, unit) {
  if (!Number.isFinite(value) || value <= 0) throw new RangeError("measurement must be a positive number");
  if (unit === "in") return value;
  if (unit === "cm") return value * IN_PER_CM;
  throw new TypeError(`unknown unit: ${unit}`);
}

function whtr({ waist, height, waistUnit = "in", heightUnit = "in" }) {
  const w = toInches(waist, waistUnit);
  const h = toInches(height, heightUnit);
  return w / h;
}

// Compare against a limit without letting float noise or display rounding flip the answer.
function withinLimit(ratio, limit) {
  const EPS = 1e-9;
  return ratio <= limit + EPS;
}

// Largest waist (inches) allowed for a given height and limit, rounded DOWN to the tape's resolution.
function maxWaist(heightIn, limit, step = 0.5) {
  const raw = heightIn * limit;
  return Math.floor((raw + 1e-9) / step) * step;
}

module.exports = { whtr, withinLimit, maxWaist, toInches };

if (require.main === module) {
  const assert = require("assert");
  // Same person, two unit systems: the ratio must agree.
  const a = whtr({ waist: 34, height: 70 });
  const b = whtr({ waist: 86.36, waistUnit: "cm", height: 177.8, heightUnit: "cm" });
  assert(Math.abs(a - b) < 1e-9);
  // Boundary: exactly on the (illustrative) limit passes, a hair over does not.
  assert(withinLimit(35 / 70, 0.5));
  assert(!withinLimit(35.1 / 70, 0.5));
  // Mixed units
  const c = whtr({ waist: 34, height: 177.8, heightUnit: "cm" });
  assert(Math.abs(c - a) < 1e-9);
  // maxWaist rounds down
  assert.strictEqual(maxWaist(69, 0.5), 34.5);
  assert.strictEqual(maxWaist(69.9, 0.5), 34.5);
  assert.strictEqual(maxWaist(70, 0.5), 35);
  // Bad input
  assert.throws(() => whtr({ waist: 0, height: 70 }), RangeError);
  assert.throws(() => whtr({ waist: 34, height: 70, waistUnit: "ft" }), TypeError);
  console.log("all tests passed", a.toFixed(4));
}

A few things worth noticing in that module.

Validation happens at the boundary. toInches rejects zero, negative numbers, NaN, and unknown units. A calculator that quietly returns Infinity or NaN teaches users to distrust the whole page. Throwing a specific error lets the UI show a specific message.

Units are explicit. Waist and height each carry their own unit, so a person can enter a waist in inches and a height in centimeters and still get the right ratio. People do this more often than you would guess, because tape measures and doctor's-office height charts don't always agree on units.

The comparison uses a small epsilon. Floating point means 35 / 70 is fine, but a value derived from a centimeter conversion might come out as 0.5000000000000001. If the rule says "at or below the limit," a person exactly on the limit must pass. The epsilon (1e-9) is far smaller than any real measurement resolution, so it only removes noise.

Rounding: decide what the user sees and what the code decides

There are two numbers in play: the exact ratio and the number you display. If you display 0.50 but the check used 0.5004, the page contradicts itself, and you will get a support email.

Two rules help:

  1. Decide pass/fail from the unrounded ratio, with the epsilon above.
  2. If the source document specifies how to round, implement that exactly and test it. If it doesn't, display more digits than you think you need, or say "rounded for display."

The maxWaist function shows the same idea in reverse. When a user asks "what is the largest waist I can have at my height," the answer should be rounded down to the resolution of a tape measure. Rounding to the nearest value could tell someone 34.75 inches is fine when 34.5 is the real limit.

Tests that catch real bugs

The assert block at the bottom of the file is short but covers the failures I have seen most:

  • Same person, different units. 34 inches at 70 inches tall must equal 86.36 cm at 177.8 cm. If this fails, your conversion constant or your order of operations is wrong.
  • Exactly on the line. The boundary case should pass, and a tenth of an inch over should not.
  • Mixed units. One input in inches and one in centimeters gives the same ratio.
  • Rounding direction. The maximum-waist helper must never round up.
  • Garbage in. Zero and unknown units throw typed errors.

If you want to go further, add a property test: for a fixed height, the ratio should never decrease as waist increases. And add a handful of cells copied by hand from the source document, so the tests don't only prove the code agrees with itself.

Presentation matters as much as math

A few UI details prevent most user mistakes:

  • Put the unit toggle next to each field, not in a settings menu.
  • Show the interpreted values back to the user ("34 in waist, 70 in height") so a typo is obvious.
  • Write the source and effective date of any threshold next to the result, and say clearly that the page is an independent tool, not an authority on the standard.

I put these ideas into a free tool, AFTPTCalc, which has a WHtR calculator alongside military fitness-test calculators. The code above is a simplified teaching version, not the production code.

Takeaways

  • Normalize units once, early, and validate hard.
  • Compare with a tiny tolerance and decide from unrounded values.
  • Round the "maximum allowed" direction deliberately.
  • Test the boundary, the unit conversions, and the garbage inputs.

If you have run into other edge cases in measurement calculators, I'd like to hear about them in the comments.

Disclosure: I used an AI writing assistant to help draft and edit this post. I ran the code and its tests before publishing. The 0.5 limit is an illustration only.

來源:DEV Community · dev.to