{
  "name": "Margin and markup from a cost and a price, down a price list",
  "page": "https://otherworldlabs.com/margin-and-markup-from-cost-and-price/",
  "endpoint": "https://otherworldlabs.com/margin-and-markup-from-cost-and-price/api.json",
  "question": "For each line of a price list whose cost and price are typed: what is the profit, what is the margin as a share of the price, what is the markup as a share of the cost, what price would the line need to reach the target typed for it on either basis, how far is the typed price from that needed price, and what is that same target written as the other ratio?",
  "answers": "Six figures per row, and the same six footed over the whole list from the footed cost and the footed price rather than from an average of the rows.",
  "version": "2026-10-24",
  "licence": "Free to read, quote and compute with. No attribution asked.",
  "method": "Every figure is arithmetic on the two figures typed for a row. Nothing is supplied, defaulted, pre-filled or suggested by the page: no cost, no price, no target, no basis, no typical or standard ratio, no competitor price, no tax, no fee and no processing charge. There is no advice of any kind here.",
  "currency": "None. Figures are printed in whatever money the reader typed; the page names no currency and applies no rate.",
  "direction": "Both. A cost and a price give the two ratios; a target and its basis give the price that reaches it, so the same two typed figures are read forwards and backwards.",
  "rounding": {
    "typed": "A cost and a price are clamped into 0 to 1,000,000 and rounded to the cent. Units are clamped into 0 to 100,000 and kept to two decimal places, the control offering whole units. A target is clamped into 0 to 1,000 and rounded to one decimal place. A blank field, a word, or anything that is not a finite number is NOT A FIGURE: it leaves the row incomplete rather than being read as nothing.",
    "half": "One rounding function, half away from zero in both directions, over the magnitude times the power of ten on the double, with nothing normalised first. That is a spreadsheet's ROUND character for character, which is what the workbook sold beside this page uses: 127.575 to 127.58, 2.675 to 2.68, 1.005 to 1.00, 0.125 to 0.13. The sign is taken out before the rounding and put back after, so a NEGATIVE HALF-CENT comes out as -0.01 and never as -0.00, where Math.round(-0.005 * 100) / 100 gives -0. A negative figure prints as -$0.01: a leading minus before the currency sign, never $-0.01 and never in brackets.",
    "ratios": "A margin and a markup are the rounded profit over the rounded denominator, times one hundred: that quotient is what the Exact fields publish, and the page prints it at ONE DECIMAL PLACE of a per cent, which is also the precision the workbook displays it to. A ratio is never clamped: a row priced beneath its own cost prints a negative margin and a negative markup, with their minus signs.",
    "two_readings": "Every ratio, every needed price and every footing is published TWICE: once as the quotient itself (the fields whose names end Exact) and once as the figure this page prints, which is that quotient at one decimal place for a ratio and at the cent for money. The page foots the figures it printed, so that a reader adding the printed column reaches the printed total; the endpoint and the workbook sold beside this page carry the unrounded chain, and the two readings of a footing differ by at most half a cent a row - which is nothing at all where the units are whole numbers, as the controls offer them. Nothing is rounded twice: the printed figure is the published figure at the printed precision. THE WAITING RULE BINDS BOTH READINGS: the clause \"0 while any live row is waiting\" written on sheetCost, sheetPrice, sheetProfit, sheetUnits, sheetNeededPrice, sheetMoneyToGo and sheetPricedTargeted binds each of the eight Exact fields in the same way and at the same states, because an Exact field is the same footing with no rounding rather than a different footing. A second implementation applies it to all sixteen, not to the seven it is written on.",
    "needed": "The price a target needs is the cost over a divisor, or times a multiplier, each rounded to three decimal places - one less the target, or one plus the target, both of which are exact at three places because a target carries one - and that quotient is what the Exact fields publish. The page prints it AT THE CENT and foots the cents it printed, so the printed expression evaluates by hand to the figure printed beside it and the printed column adds to the printed total."
  },
  "clamping": "A figure outside a range is brought to the nearer end of that range rather than refused, except for one state that is refused with its reason: a target read as a margin above 95 per cent, where the price that reaches it runs away without limit and at 100 per cent no price reaches it at all. Such a row prints no needed price, no money to go and no converted ratio, and says why.",
  "inputs": [
    {
      "name": "rowCount",
      "type": "number",
      "required": false,
      "range": "1 to 12, whole numbers",
      "note": "How many rows of the price list are live. It is the one figure on the page the page itself holds, because it is a form control and not a price. Rows past this count are put away and foot nowhere. Left out, the count is the number of rows given, held to 1 to 12."
    },
    {
      "name": "rows",
      "type": "array",
      "required": true,
      "range": "1 to 12 objects",
      "note": "One object per line of the price list, each carrying name, cost, price, units, target and basis as described below. A member past rowCount is ignored; a member missing inside the count is read as a row nobody has typed yet."
    },
    {
      "name": "rows[].name",
      "type": "string",
      "required": false,
      "range": "up to 40 characters",
      "note": "The reader's own name for the line, off their shelf tag, invoice line or SKU list. Blank or whitespace-only, the row is called by its number (Row 1, Row 2). It is echoed and nothing else: no figure depends on it."
    },
    {
      "name": "rows[].cost",
      "type": "number",
      "required": true,
      "range": "0 to 1000000, step 0.01",
      "note": "What one unit of that line cost, off the supplier invoice or quote. Typed every time: the page holds no cost of its own. A cost of zero is a legal figure and leaves the markup undefined, because a markup is a share of the cost."
    },
    {
      "name": "rows[].price",
      "type": "number",
      "required": true,
      "range": "0 to 1000000, step 0.01",
      "note": "What one unit of that line is priced at, off the shelf tag, the wholesale sheet or the quote being weighed. A price of zero is a legal figure and leaves the margin undefined, because a margin is a share of the price. A price beneath the cost gives a negative profit, margin and markup, and all three print as negatives."
    },
    {
      "name": "rows[].units",
      "type": "number",
      "required": false,
      "range": "0 to 100000, step 1",
      "note": "How many of that line the sheet foots over. Blank foots one of the row, which is what a per-unit list means. A count of zero foots nothing for that row while leaving its own six figures printed. The control offers whole units; a figure carrying a fraction, which reaches the engine from the workbook sold beside this page or from a pasted cell rather than from the control, is clamped and kept to two decimal places and printed as it is rather than rounded into a whole unit behind the reader."
    },
    {
      "name": "rows[].target",
      "type": "number",
      "required": false,
      "range": "0 to 1000, step 0.1; the control offers 0 to 95 while the basis is margin",
      "note": "The ratio the reader is trying to hit on that row, in per cent. Theirs, never suggested by the page. Read as a margin it is offered as far as 95; read as a markup it is offered as far as 1,000. A target with no basis chosen computes nothing."
    },
    {
      "name": "rows[].basis",
      "type": "string",
      "required": false,
      "range": "\"\", \"margin\" or \"markup\"",
      "note": "Which ratio the typed target is read as: a margin is a share of the price, a markup is a share of the cost. The first option of the control is unchosen, so the page never picks a basis for a reader. The long option labels of the page's own select and the choice cell of the workbook are both read, so the engine does not know which it was given."
    },
    {
      "name": "rowName, rowCost, rowPrice, rowUnits, rowTarget, rowBasis",
      "type": "number or string at the root",
      "required": false,
      "range": "as the matching rows[] field",
      "note": "The one-line form. The workbook sold beside this page keeps a line per priced row, so a fill of that workbook arrives as these root fields and is folded into a one-row sheet before anything is computed, which is how the kit's figures and this page's figures are compared cell by cell."
    }
  ],
  "outputs": [
    { "name": "rowsLive", "unit": "rows", "formula": "the row count held to 1 to 12, which is how many rows of the list are live and footed over" },
    { "name": "rowsCounted", "unit": "rows", "formula": "of the live rows, the count carrying both a cost and a price, which are the only rows that enter any footing" },
    { "name": "rowsWaiting", "unit": "rows", "formula": "rowsLive - rowsCounted. While it is above 0 no footed figure prints at all and the page prints this count instead" },
    { "name": "rowsWithTarget", "unit": "rows", "formula": "the count of live rows that are complete and carry a target with a basis chosen and accepted, so a needed price exists for them" },
    { "name": "rowsPricedBelowCost", "unit": "rows", "formula": "the count of complete rows whose profit is below zero, which is the state printed as a negative margin and a negative markup rather than hidden" },
    { "name": "rowsPastTarget", "unit": "rows", "formula": "the count of rows whose money to go is below zero, meaning the typed price is already past the price the target asks for" },
    { "name": "rowsWithTargetRefused", "unit": "rows", "formula": "the count of live rows asking for a margin target above 95 per cent, which the engine refuses with its reason instead of printing a price. THE ROW NEED NOT BE COMPLETE: unlike rowsWithTarget above, which counts only rows carrying both a cost and a price, a refused target is counted as soon as the target and the margin basis are both on the row, because the refusal is about the target alone and is printed whether or not the row has a cost or a price yet" },
    { "name": "sheetCost", "unit": "money", "formula": "the sum over the counted rows of the cost times the units used, each product rounded to the cent before it is added; 0 while any live row is waiting" },
    { "name": "sheetPrice", "unit": "money", "formula": "the sum over the counted rows of the price times the units used, each product rounded to the cent before it is added; 0 while any live row is waiting" },
    { "name": "sheetProfit", "unit": "money", "formula": "sheetPrice - sheetCost, which is the profit of the whole list; 0 while any live row is waiting" },
    { "name": "sheetUnits", "unit": "units", "formula": "the sum over the counted rows of the units used, where a blank units field counts as one of that row; 0 while any live row is waiting" },
    { "name": "sheetMargin", "unit": "per cent", "formula": "sheetProfit over sheetPrice times 100, to one decimal place: the footed margin, taken from the footed figures and never averaged over the rows. 0 where the footed price is nothing, in which case no margin is defined and the page says so in words" },
    { "name": "sheetMarkup", "unit": "per cent", "formula": "sheetProfit over sheetCost times 100, to one decimal place: the footed markup, taken from the footed figures and never averaged over the rows. 0 where the footed cost is nothing, in which case no markup is defined and the page says so in words" },
    { "name": "sheetNeededPrice", "unit": "money", "formula": "the sum over the rows carrying an accepted target of the needed price times the units used, each product rounded to the cent; 0 while any live row is waiting" },
    { "name": "sheetMoneyToGo", "unit": "money", "formula": "the sum over those same rows of the money to go times the units used, signed, so a list already past its targets foots to a negative figure; 0 while any live row is waiting" },
    { "name": "highestRowMargin", "unit": "per cent", "formula": "the largest margin printed on any live row, or 0 where no row has a margin defined" },
    { "name": "lowestRowMargin", "unit": "per cent", "formula": "the smallest margin printed on any live row, which may be negative, or 0 where no row has a margin defined" },
    { "name": "highestRowMarkup", "unit": "per cent", "formula": "the largest markup printed on any live row, or 0 where no row has a markup defined. It has no ceiling: a penny of cost against a thousand of price is 9,999,900.0" },
    { "name": "lowestRowMarkup", "unit": "per cent", "formula": "the smallest markup printed on any live row, which may be negative, or 0 where no row has a markup defined" },
    { "name": "sheetCostExact", "unit": "money", "formula": "the footed cost with no rounding at any step: the sum of each counted row's cost times its units. sheetCost is the same footing taken over the cents the page PRINTS, so that a reader adding the printed column reaches the printed total; with a whole count of units, which is what the control offers, the two are the same figure" },
    { "name": "sheetPriceExact", "unit": "money", "formula": "the footed price with no rounding at any step: the sum of each counted row's price times its units, of which sheetPrice is the printed-cents reading" },
    { "name": "sheetProfitExact", "unit": "money", "formula": "sheetPriceExact - sheetCostExact, the footed profit with no rounding at any step" },
    { "name": "sheetPricedTargetedExact", "unit": "money", "formula": "what the rows carrying an accepted target are priced at, footed with no rounding at any step, of which sheetPricedTargeted is the printed-cents reading" },
    { "name": "sheetMarginExact", "unit": "per cent", "formula": "the same footed margin as the quotient itself, with no rounding: sheetProfitExact over sheetPriceExact times 100, IN THAT ASSOCIATION - the quotient is taken first and the product with 100 second, (a / b) * 100 and never (100 * a) / b, because the two differ in the sixteenth significant figure of a double and a second implementation has to land on the same bit. 0 while any live row is waiting, which binds every Exact field exactly as it binds the printed one. It is this figure that the workbook sold beside this page carries in its own cell and that sheetMargin is the one-decimal-place reading of" },
    { "name": "sheetMarkupExact", "unit": "per cent", "formula": "the same footed markup as the quotient itself, with no rounding: sheetProfitExact over sheetCostExact times 100, IN THAT ASSOCIATION - (a / b) * 100 and never (100 * a) / b, for the reason given on sheetMarginExact. 0 while any live row is waiting, which binds every Exact field exactly as it binds the printed one. It is the figure of which sheetMarkup is the one-decimal-place reading" },
    { "name": "sheetNeededPriceExact", "unit": "money", "formula": "the needed price footed over the targeted rows with no rounding at all: the sum of each row's own quotient times its units. sheetNeededPrice is the same footing taken over the figures the page PRINTS, each rounded to the cent first, so that a reader adding the printed column reaches the printed total; the two differ by at most half a cent a row" },
    { "name": "sheetMoneyToGoExact", "unit": "money", "formula": "the money to go footed over the same rows with no rounding, signed: the sum of each row's own quotient less its typed price, times its units" },
    { "name": "sheetPricedTargeted", "unit": "money", "formula": "what the rows carrying an accepted target are priced at, footed the same way as sheetPrice but over those rows alone, so that a sentence about what those rows need is a sentence about the same rows on both sides of it; 0 while any live row is waiting" },
    { "name": "waiting", "unit": "true or false", "formula": "true while any live row is missing a cost or a price. While it is true the headline prints no figure of the list at all, and the sentence saying how many rows are still waiting prints in the glance" },
    { "name": "rowProfit1", "unit": "money", "formula": "ROW 1's instance of the per-row family rowProfit<n> below, named here as an output in its own right. Rows 2 to 12 are published under the <n> template and are returned only while that row is live; row 1 is always live, because the row count is held to 1 to 12, so these six are the six fields every answer carries whatever the state. The formula is the family's: the price less the cost on row 1, rounded to the cent; 0 where the row is not complete" },
    { "name": "rowMargin1", "unit": "per cent", "formula": "ROW 1's instance of rowMargin<n>: rowProfit1 over the price on row 1 times 100, to one decimal place; 0 where the row is not complete or its price is nothing, which is where no margin is defined. Named as an output for the reason given on rowProfit1" },
    { "name": "rowMarkup1", "unit": "per cent", "formula": "ROW 1's instance of rowMarkup<n>: rowProfit1 over the cost on row 1 times 100, to one decimal place; 0 where the row is not complete or its cost is nothing, which is where no markup is defined. Named as an output for the reason given on rowProfit1" },
    { "name": "rowNeededPrice1", "unit": "money", "formula": "ROW 1's instance of rowNeededPrice<n>: the cost on row 1 divided by one less the target where the basis is margin, or times one plus the target where it is markup, to the cent; 0 where the row is not complete or carries no accepted target. Named as an output for the reason given on rowProfit1" },
    { "name": "rowMoneyToGo1", "unit": "money", "formula": "ROW 1's instance of rowMoneyToGo<n>: rowNeededPrice1 less the typed price on row 1, signed, so a row already past its target is negative; 0 where the row is not complete or carries no accepted target. Named as an output for the reason given on rowProfit1" },
    { "name": "rowTargetAsOther1", "unit": "per cent", "formula": "ROW 1's instance of rowTargetAsOther<n>: the target on row 1 written as the other ratio, the target over one hundred less the target where the basis is margin, or over one hundred plus the target where it is markup, times 100 and to one decimal place; 0 where the row carries no accepted target. Named as an output for the reason given on rowProfit1" }
  ],
  "outputs_per_row": [
    { "name": "rowProfit<n>", "unit": "money", "formula": "the price less the cost on row n, rounded to the cent; 0 where the row is not complete. n runs from 1 to rowsLive" },
    { "name": "rowMargin<n>", "unit": "per cent", "formula": "rowProfit<n> over the price on row n times 100, to one decimal place; 0 where the row is not complete or its price is nothing, which is where no margin is defined" },
    { "name": "rowMarkup<n>", "unit": "per cent", "formula": "rowProfit<n> over the cost on row n times 100, to one decimal place; 0 where the row is not complete or its cost is nothing, which is where no markup is defined" },
    { "name": "rowNeededPrice<n>", "unit": "money", "formula": "the cost on row n divided by one less the target where the basis is margin, or times one plus the target where it is markup, to the cent; 0 where the row is not complete or carries no accepted target" },
    { "name": "rowMoneyToGo<n>", "unit": "money", "formula": "rowNeededPrice<n> less the typed price on row n, signed, so a row already past its target is negative; 0 where the row is not complete or carries no accepted target" },
    { "name": "rowTargetAsOther<n>", "unit": "per cent", "formula": "the same target written as the other ratio: the target over one hundred less the target where the basis is margin, or the target over one hundred plus the target where it is markup, each times 100 and to one decimal place; 0 where the row carries no accepted target" }
  ],
  "formula": "profit = price - cost. margin = 100 x profit / price. markup = 100 x profit / cost. needed price, read as a margin = cost / (1 - target/100); read as a markup = cost x (1 + target/100). money to go = needed price - price. the target as the other ratio, from a margin = 100 x target / (100 - target); from a markup = 100 x target / (100 + target). The footed figures are the same arithmetic over the footed cost and the footed price.",
  "identities": [
    "markup is never below margin wherever both are defined, because markup - margin = 100 x profit x profit / (cost x price) WHEN BOTH ARE READ IN PER CENT, which is the unit every field here publishes them in; as plain shares rather than per cents the same gap is profit x profit / (cost x price), without the hundred. Either way it is nothing only where the profit is nothing.",
    "the UNROUNDED margin reaches 100 per cent only where the cost is nothing, and the unrounded markup reaches -100 per cent only where the price is nothing. The PRINTED margin, which is the one-decimal-place reading in sheetMargin and rowMargin<n>, reaches 100.0 wherever the cost is small enough beside the price for the quotient to round there: a cost of 0.01 against a price of 1,000.00 is 99.999 per cent and prints 100.0. Where the price is nothing no margin is defined at all and none is printed, and where the cost is nothing no markup is defined; those are null in the model and 0 in the published field, with the reason printed in words.",
    "a target read as a margin and the same target written as a markup give the same needed price, so the two conversions and the two inversions are one piece of arithmetic read two ways.",
    "the footed profit is the footed price less the footed cost, and the footed margin is not the average of the rows' margins.",
    "money to go is nothing exactly where the typed price is the price the target asks for."
  ],
  "example": {
    "note": "Three rows: a mug priced at 9.00 on a cost of 4.20 with 12 units and a 40 per cent margin target; an unnamed row priced at 7.50 on a cost of 10.00 with 3 units and a 25 per cent markup target, which is the price-below-cost state; and a case priced at 1,000.00 on a cost of 0.01 with a 95 per cent margin target, which is the top of the offered margin range and the largest markup the arithmetic can reach from a penny.",
    "inputs": {
      "rowCount": 3,
      "rows": [
        { "name": "Blue mug", "cost": 4.2, "price": 9, "units": 12, "target": 40, "basis": "margin" },
        { "name": "", "cost": 10, "price": 7.5, "units": 3, "target": 25, "basis": "markup" },
        { "name": "wholesale case", "cost": 0.01, "price": 1000, "units": 1, "target": 95, "basis": "margin" }
      ]
    },
    "outputs": {
      "rowsLive": 3,
      "rowsCounted": 3,
      "rowsWaiting": 0,
      "rowsWithTarget": 3,
      "rowsPricedBelowCost": 1,
      "rowsPastTarget": 2,
      "rowsWithTargetRefused": 0,
      "sheetCost": 80.41,
      "sheetPrice": 1130.5,
      "sheetProfit": 1050.09,
      "sheetUnits": 16,
      "sheetMargin": 92.9,
      "sheetMarkup": 1305.9,
      "sheetNeededPrice": 121.7,
      "sheetMoneyToGo": -1008.8,
      "sheetCostExact": 80.41000000000001,
      "sheetPriceExact": 1130.5,
      "sheetProfitExact": 1050.09,
      "sheetPricedTargetedExact": 1130.5,
      "sheetMarginExact": 92.88721804511277,
      "sheetMarkupExact": 1305.919661733615,
      "sheetNeededPriceExact": 121.70000000000002,
      "sheetMoneyToGoExact": -1008.8,
      "sheetPricedTargeted": 1130.5,
      "highestRowMargin": 100,
      "lowestRowMargin": -33.3,
      "highestRowMarkup": 9999900,
      "lowestRowMarkup": -25,
      "waiting": false,
      "rowProfit1": 4.8,
      "rowMargin1": 53.3,
      "rowMarkup1": 114.3,
      "rowNeededPrice1": 7,
      "rowMoneyToGo1": -2,
      "rowTargetAsOther1": 66.7,
      "rowProfit2": -2.5,
      "rowMargin2": -33.3,
      "rowMarkup2": -25,
      "rowNeededPrice2": 12.5,
      "rowMoneyToGo2": 5,
      "rowTargetAsOther2": 20,
      "rowProfit3": 999.99,
      "rowMargin3": 100,
      "rowMarkup3": 9999900,
      "rowNeededPrice3": 0.2,
      "rowMoneyToGo3": -999.8,
      "rowTargetAsOther3": 1900
    }
  },
  "not_covered": [
    "What any ratio should be. There is no typical, standard, industry or healthy figure here, and no recommended price, margin or markup.",
    "What anything costs or is priced at anywhere else. No competitor price, no market rate, no benchmark.",
    "Tax of any kind, duty, platform or marketplace fees, payment processing, discounts, carriage and returns.",
    "Whether a price will sell, whether a business is viable, and what a business can afford.",
    "What belongs in a cost. A landed cost and an invoice cost give different ratios on the same price, and which one a reader means is a decision the page prints either way."
  ],
  "no_advice": "This is arithmetic on figures the reader types. It is not pricing, business, financial, tax or legal advice, and it recommends nothing."
}
