Short answer: a peptide reconstitution calculator does four lines of arithmetic: concentration = mass ÷ diluent volume, volume = amount ÷ concentration, U-100 syringe units = mL × 100, and count per vial = vial mass ÷ amount. The arithmetic is easy. Most bugs come from the parts around it: unit conversion (mg vs mcg vs IU), floating-point display, rounding to a syringe scale, and inputs that should be rejected rather than "calculated".

Disclosure: I'm affiliated with Peptide Library, which is published by Superfime LLC and runs a free web calculator. This post is about the engineering, and the code below is standalone. Everything here is for research and lab recordkeeping only. It is not medical advice, and no number in this post is a suggested amount of anything.

Why this is worth a post

Search demand for this kind of tool is large. Ahrefs' US data (November 2026) shows about 11,000 monthly searches for "peptide reconstitution calculator" and over 200,000 for "peptide calculator". Most of the tools that rank are single-page forms with no tests and no visible formulas. If you build one, the bar for correctness should be higher than "it printed a number".

This post walks through the model, the unit handling, a TypeScript implementation, and the test cases I'd consider the minimum.

The model: four formulas and one unit rule

A lyophilized (freeze-dried) vial holds a fixed mass of powder. Adding a known volume of diluent turns it into a solution with a known concentration. From there, everything follows:

Quantity Formula Unit
Concentration vial mass ÷ diluent volume mg/mL
Volume for an amount amount ÷ concentration mL
U-100 syringe units volume × 100 units
Count per vial vial mass ÷ amount (dimensionless)

The unit rule is that everything is converted to one internal mass unit before any division. Most visible errors in these tools come from mixing milligrams and micrograms. 1 mg = 1,000 mcg, by SI prefix definition. NIST's metric prefix reference is a good thing to link in your code comments so nobody "fixes" it later.

IU (international units) is different. IU is not an SI unit, and the mass-per-IU factor is specific to each substance. A calculator should never assume a universal IU-to-mg factor. Either require the factor as an input or attach it to a specific, documented reference entry. If you don't have a factor, refuse the conversion.

What "U-100" means for the display

A U-100 insulin syringe is graduated so that 100 units = 1 mL, which is why the conversion is a factor of 100. The syringe itself constrains the output. Common barrel sizes are 0.3 mL, 0.5 mL and 1 mL, and the readable gradation differs by barrel. A good calculator shows the exact computed value and flags when that value falls between markings, instead of silently rounding.

A typed implementation

Here's a minimal, dependency-free TypeScript version. It works in integer micrograms internally and returns structured results with warnings.

type MassUnit = "mg" | "mcg"; const MCG_PER: Record<MassUnit, number> = { mg: 1000, mcg: 1 }; export interface ReconInput { vialMass: number; // e.g. 5 vialUnit: MassUnit; // "mg" diluentMl: number; // e.g. 2.5 amount: number; // e.g. 500 amountUnit: MassUnit; // "mcg" syringeMl?: 0.3 | 0.5 | 1; } export interface ReconResult { concentrationMgPerMl: number; volumeMl: number; u100Units: number; countPerVial: number; warnings: string[]; } const toMcg = (v: number, u: MassUnit) => v * MCG_PER[u]; export function reconstitute(i: ReconInput): ReconResult { const warnings: string[] = []; for (const [k, v] of Object.entries(i)) { if (typeof v === "number" && (!Number.isFinite(v) || v <= 0)) { throw new RangeError(`${k} must be a positive, finite number`); } } const vialMcg = toMcg(i.vialMass, i.vialUnit); const amountMcg = toMcg(i.amount, i.amountUnit); if (amountMcg > vialMcg) { throw new RangeError("amount exceeds the total mass in the vial"); } const concMcgPerMl = vialMcg / i.diluentMl; const volumeMl = amountMcg / concMcgPerMl; const units = volumeMl * 100; const syringe = i.syringeMl ?? 1; if (volumeMl > syringe) { warnings.push(`volume ${volumeMl.toFixed(3)} mL exceeds a ${syringe} mL barrel`); } if (units < 1) { warnings.push("result is below 1 unit on a U-100 scale; hard to read accurately"); } return { concentrationMgPerMl: concMcgPerMl / 1000, volumeMl, u100Units: units, countPerVial: vialMcg / amountMcg, warnings, }; } 

A few deliberate choices:

  • Reject, don't coerce. Zero, negative, NaN and Infinity inputs throw. A calculator that returns Infinity mL because someone typed 0 for diluent is worse than one that shows an error.
  • Amount larger than vial is an error, not a result of "0.8 vials".
  • Warnings are data, so the UI can render them next to the number instead of burying them in a toast.

Floating point: where "correct" math prints wrong numbers

JavaScript numbers are IEEE 754 doubles, so 0.1 + 0.2 !== 0.3. Python's docs have the clearest short explanation of why: see Floating-Point Arithmetic: Issues and Limitations. For a calculator this shows up two ways:

  1. Display drift. A volume computed as 0.25000000000000006 should display as 0.25. Format at the edge (toFixed, Intl.NumberFormat) and keep full precision internally.
  2. Comparisons. Never compare computed doubles with === in tests. Use a tolerance. Number.EPSILON (MDN) is the gap between 1 and the next representable double. For values around 1-100, a relative tolerance like 1e-9 is more practical.
export const close = (a: number, b: number, rel = 1e-9) => Math.abs(a - b) <= rel * Math.max(1, Math.abs(a), Math.abs(b)); 

Display rules that prevent misreads

Medication-safety groups have documented for decades how numbers get misread. ISMP's (now ECRI's) list of error-prone abbreviations and dose designations is directly relevant to UI design, even for research tools:

  • No trailing zeros (5.0 can be read as 50).
  • Always a leading zero (.5 can be read as 5; show 0.5).
  • Avoid "µg", which is easy to misread as "mg". Spell out "mcg".
  • Put the unit right next to every number. A bare "0.25" in a results card is ambiguous.

I'd encode these as a formatter and test it as carefully as the math:

export function fmt(n: number, unit: string, maxDp = 3): string { const s = n.toFixed(maxDp).replace(/\.?0+$/, ""); // strip trailing zeros return `${s.startsWith(".") ? "0" + s : s} ${unit}`; } 

Test vectors

These are arbitrary arithmetic fixtures. They check the math and are not amounts anyone should use.

Vial Diluent Amount Expected conc. Expected volume Expected units Count
5 mg 2.5 mL 500 mcg 2 mg/mL 0.25 mL 25 10
10 mg 4 mL 1 mg 2.5 mg/mL 0.4 mL 40 10
2 mg 1 mL 100 mcg 2 mg/mL 0.05 mL 5 20
import { reconstitute } from "./recon"; import { close } from "./close"; // the tolerance helper above test("5 mg / 2.5 mL / 500 mcg", () => { const r = reconstitute({ vialMass: 5, vialUnit: "mg", diluentMl: 2.5, amount: 500, amountUnit: "mcg" }); expect(close(r.concentrationMgPerMl, 2)).toBe(true); expect(close(r.volumeMl, 0.25)).toBe(true); expect(close(r.u100Units, 25)).toBe(true); expect(close(r.countPerVial, 10)).toBe(true); }); test("rejects zero diluent", () => { expect(() => reconstitute({ vialMass: 5, vialUnit: "mg", diluentMl: 0, amount: 1, amountUnit: "mg" })) .toThrow(RangeError); }); test("rejects amount larger than vial", () => { expect(() => reconstitute({ vialMass: 1, vialUnit: "mg", diluentMl: 1, amount: 2, amountUnit: "mg" })) .toThrow(RangeError); }); 

Other cases worth covering:

  • Unit symmetry: amount: 0.5, amountUnit: "mg" must equal amount: 500, amountUnit: "mcg".
  • Round trip: volume × concentration should return the original amount within tolerance.
  • Property tests: for random positive inputs, countPerVial × amount === vialMass (within tolerance). A tool like fast-check makes this cheap.
  • Formatter: fmt(0.5, "mL") === "0.5 mL", fmt(5, "mg") === "5 mg" (no 5.000), and .25 never appears.

What the calculator can't tell you

It's worth being explicit, in the UI and in docs, about what the arithmetic doesn't cover:

  • It doesn't verify what's in the vial. The mass on a label is a claim. Purity and identity testing is a separate lab question, and a correct calculation on a mislabeled vial is still wrong.
  • It is not evidence of anything. For many research peptides, published data is mostly preclinical (cell and animal studies), with little or no controlled human data. The FDA's page on bulk drug substances that may present significant safety risks in compounding lists several popular peptides, including BPC-157, and notes limited or missing safety information. Keep "the math is right" and "the substance is studied in humans" clearly apart in your copy.
  • It's not a recommendation engine. A research calculator should take the amount as user input and never suggest one.

Platform note: why some iOS apps dropped calculators

If you ship this in a native iPhone app, read Apple's App Review Guideline 1.4.2. It says drug dosage calculators must come from a drug manufacturer, hospital, university, health insurer, pharmacy or other approved entity, or be approved by the FDA or an international counterpart. Most independent developers are none of those things. Peptide Library is one app that changed course: its App Store release notes for version 2.9.4 say it "removed all the calculators from the iPhone app to comply with App Store policy".

The browser is a different distribution channel. A web calculator can be added to the iPhone Home Screen as a web app, which is the route Peptide Library's web calculator documents for iOS users. If you go that way, the testing above matters even more, because no app review sits between your code and the user.

What I learned

  • Write the formulas into the UI. When users can see "mass ÷ volume = concentration", they catch their own input mistakes.
  • Spend as much time on rejection paths and formatting as on the math. They're where the real-world errors are.
  • Keep the domain claims out of the calculator. Its job is arithmetic, clearly labeled, with units on every number.

If you want to compare your implementation's output against a working reference, the online peptide calculator on Peptide Library shows the same four formulas on the page. It's a quick way to sanity-check a test vector.

Research and educational use only. Not medical advice. Nothing in this post is a recommended amount, schedule or use of any substance. Affiliation: Peptide Library is published by Superfime LLC.