{
  "category": "Text functions",
  "display_name": "SUBSTITUTE",
  "entry_kind": "built_in_function",
  "eval_api": {
    "method": "POST",
    "url": "https://handbook.dnacalc.com/api/eval"
  },
  "function_id": "FUNC.SUBSTITUTE",
  "implementation_state": {
    "answer_bank": null,
    "detail": "Recorded: 9 workbook cells.",
    "dnacalc_commit": "9e1884369a678158dd82d3d74188daf986995e87",
    "in_catalogue": true,
    "state": "matches",
    "state_label": "Matches every recorded Excel answer",
    "summary": "Matches all 9 recorded answers",
    "workbook_corpus": {
      "cells": 9,
      "matched": 9
    }
  },
  "implementations": [
    {
      "commit": "9e1884369a678158dd82d3d74188daf986995e87",
      "flavour": "excel-compatibility-candidate",
      "language": "Rust",
      "name": "CalcFunc",
      "own_code": [
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L312-L325",
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/ported/text_kernels.rs#L202-L230"
      ],
      "repository": "https://github.com/DnaCalc/DnaCalc",
      "shared_code": [
        "catalogue/text_functions::capped",
        "ported/text_kernels::text",
        "ported/text_kernels::truncated",
        "ported/text_kernels::positive"
      ],
      "source": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text_functions.rs#L312-L325",
      "state": "matches",
      "suite": null,
      "summary": "Matches all 9 recorded answers"
    }
  ],
  "json_url": "https://handbook.dnacalc.com/api/functions/FUNC.SUBSTITUTE.json",
  "name": "SUBSTITUTE",
  "notes": [],
  "schema": "efh.function-reference/v1",
  "section_order": [
    "what-it-does",
    "try-it",
    "quirks",
    "how-to-implement",
    "version-differences",
    "alternatives"
  ],
  "sections": {
    "alternatives": "\n### Relationships\n\n\n- **[REPLACE](FUNC.REPLACE.md)** — the by-position counterpart. See the comparison above.\n- **[SEARCH](FUNC.SEARCH.md) and `FIND`** — the locators. Both differ from `SUBSTITUTE` on the\n  matching axis: `SEARCH` is case-insensitive with wildcards, `FIND` is case-sensitive and\n  literal, `SUBSTITUTE` is case-sensitive and literal. `FIND` is the locator whose matching\n  rule actually agrees with `SUBSTITUTE`'s.\n- **`REGEXREPLACE`** is the modern general-purpose alternative, with a real pattern language,\n  case options and capture groups. `SUBSTITUTE` is not superseded — literal replacement is\n  still its own job, and it is the cheaper and clearer tool when that is all you need.\n- **`TRIM` and `CLEAN`** are the fixed-purpose cleaners; `SUBSTITUTE(text, CHAR(160), \" \")` is\n  the standard idiom for the non-breaking spaces that `TRIM` does not touch, which is probably\n  this function's single most common real-world use.\n- **`LEN(text) - LEN(SUBSTITUTE(text, x, \"\"))`** is the classic occurrence-counting idiom, and\n  it works precisely because `SUBSTITUTE` returns the input unchanged when there is nothing to\n  do.\n\n\n",
    "how-to-implement": "\n### Notes for implementers\n\n\nScanning must be single-pass and left-to-right, with the scan position advancing past\n`new_text` rather than re-entering it. Otherwise `SUBSTITUTE(\"a\", \"a\", \"aa\")` does not\nterminate.\n\n`instance_num` counting has to be done on the *original* occurrences, established by the same\nnon-overlapping left-to-right scan. Counting occurrences in a partially rewritten string gives\ndifferent answers.\n\nMatching is over UTF-16 code units. That makes the match byte-alignment-free but also means an\n`old_text` that is half a surrogate pair can match, which is a state the value-universe chapter\nrecords as reachable.\n\n\n",
    "quirks": "\n### SUBSTITUTE versus REPLACE\n\n\nThe most-confused pair in the text category. Microsoft draws the line on the `SUBSTITUTE` page\nitself:\n\n> Use `SUBSTITUTE` when you want to replace specific text in a text string; use `REPLACE` when\n> you want to replace any text that occurs in a specific location in a text string.\n\n| | `SUBSTITUTE` | `REPLACE` |\n|---|---|---|\n| Addresses the target by | **content** — `old_text`, matched literally | **position** — `start_num`, `num_chars` |\n| Number of edits | all occurrences, or one chosen by `instance_num` | exactly one window |\n| Target absent | returns the input unchanged | not applicable — nothing is searched for |\n| Case sensitivity | case sensitive | not applicable |\n| Wildcards | none | not applicable |\n| Byte sibling | none | `REPLACEB` |\n\nThe absence of a `SUBSTITUTEB` is itself informative: by-content editing never needs a byte\ncoordinate, so the DBCS variant that the positional family requires has no counterpart here.\nUnder DBCS, `SUBSTITUTE` is generally the safer of the two for exactly that reason.\n\nThe one-line test: if your formula computes `start_num` from a `FIND` or `SEARCH` result, you\nwanted `SUBSTITUTE`. If your positions come from a fixed-width record layout, you wanted\n[REPLACE](FUNC.REPLACE.md).\n\n\n\n### Result and edge cases\n\n\nReturns `Text`.\n\nDocumented: the `instance_num` semantics above, and the by-content matching rule. Microsoft's\npage does not enumerate boundary conditions, so the following are open rather than settled:\n\n- **Empty `old_text`.** `SUBSTITUTE(\"abc\", \"\", \"X\")` has no obviously correct answer — an empty\n  match occurs everywhere. Unverified.\n- **Empty `new_text`.** Deletion, by the obvious reading, and the idiomatic way to strip a\n  substring. Not stated on the page.\n- **`instance_num` greater than the number of occurrences**, or zero, or negative. Unchanged\n  input and `#VALUE!` are both plausible for the first; the last two are unaddressed.\n- **Overlapping occurrences**, as in `SUBSTITUTE(\"aaa\", \"aa\", \"X\")`. Left-to-right\n  non-overlapping scanning is the usual convention and is not documented here.\n- **Whether replacement is re-scanned.** If `new_text` contains `old_text`, a naive\n  implementation can loop. Single-pass scanning is the sane behaviour and is not stated.\n\n\n### Errors\n\n\nMicrosoft's `SUBSTITUTE` page carries no error table. The reachable errors are the shared ones:\nan error value in any argument propagates, non-numeric text in `instance_num` surfaces\n`#VALUE!` under the shared coercion rules, and a result exceeding the 32,767 code-unit text cap\nis a candidate — the Handbook's value-universe chapter records `#VALUE!` for the over-cap\nformula path observed with `REPT`, but whether `SUBSTITUTE` publishes the same way is\nunverified.\n\n`SUBSTITUTE` can grow its input without limit — replacing `\"a\"` with a long string in a long\ntext — so the cap is genuinely reachable, not theoretical.\n\n\n",
    "sources": "\n- Microsoft, \"SUBSTITUTE function\" —\n  <https://support.microsoft.com/en-us/office/substitute-function-6434944e-a904-4336-a9b0-1e58df3bc332>\n  (signature, the `instance_num` semantics, and the quoted by-content versus by-location\n  guidance).\n- Microsoft, \"REPLACE, REPLACEB functions\" —\n  <https://support.microsoft.com/en-us/office/replace-function-8d799074-2425-4a8a-84bc-82472868878a>\n  (the other side of the pair).\n- Handbook, [The value universe](../model/01-value-universe.md) — the 32,767 code-unit text\n  cap and the observed `#VALUE!` on the over-cap formula path.\n- Handbook, [Coercion and lifting](../model/02-coercion-and-lifting.md) — argument coercion and\n  error propagation.\n- Handbook projections `data/functions/FUNC.SUBSTITUTE.json` (placeholder signature) and\n  `data/presence/FUNC.SUBSTITUTE.json` (implementing module; `BUG-FUNC-008`, `BUG-FUNC-016`).\n\n",
    "version-differences": "No comparison between specific Excel builds is recorded here yet. This does not establish that all builds behave alike.",
    "what-it-does": "\n`SUBSTITUTE(text, old_text, new_text, [instance_num])` returns `text` with occurrences of\n`old_text` rewritten as `new_text`.\n\nTwo rules define it, and both are documented:\n\n1. **`old_text` is matched by content, literally.** The match is **case sensitive** and there\n   is **no wildcard language** — a `*` in `old_text` is an asterisk.\n2. **Without `instance_num`, every occurrence is replaced. With `instance_num`, only that one\n   occurrence is.** Microsoft states this directly: if you specify `instance_num`, only that\n   instance of `old_text` is replaced; otherwise every occurrence in `text` is changed.\n\n`SUBSTITUTE` never fails to find something. If `old_text` does not occur in `text`, the result\nis `text` unchanged — not an error, not an empty string. This is the opposite of `FIND` and\n`SEARCH`, which raise `#VALUE!` when the target is absent, and it is why `SUBSTITUTE` is safe\nto apply speculatively while a `SEARCH` needs an `IFERROR` guard.\n\nNote the case rule carefully, because it points the wrong way from most readers' intuition:\n`SEARCH` — the *finding* function most people reach for — is case-**in**sensitive, while\n`SUBSTITUTE` — the *editing* function — is case-**sensitive**. A formula that tests with\n`SEARCH` and edits with `SUBSTITUTE` is applying two different matching rules to the same\nstring, and will happily confirm that a substring is present and then decline to replace it.\n\n\n\n### Arguments\n\n\n| Argument | Meaning | Default |\n|---|---|---|\n| `text` | The string to edit. Required. | — |\n| `old_text` | The literal substring to find. Required. | — |\n| `new_text` | What to put in its place. Required. | — |\n| `instance_num` | Which occurrence to replace, counted from the left. Optional. | all occurrences |\n\n`instance_num` is the position most often misread. It is an **occurrence index**, not a\ncharacter position and not a count of replacements to make. `instance_num` of 2 replaces the\nsecond occurrence and leaves the first and third alone.\n\n`instance_num` is a numeric slot subject to ordinary to-number coercion; the other three are\ntext slots. See [Coercion and lifting](../model/02-coercion-and-lifting.md).\n\n"
  },
  "signature": {
    "display": "SUBSTITUTE(text, old_text, new_text, [instance_num])",
    "parameters": [
      {
        "description": null,
        "name": "text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "old_text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "new_text",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "instance_num",
        "optional": true,
        "repeats": false
      }
    ],
    "trailing_repeats": false
  },
  "sources": {
    "metadata": {
      "admission": {
        "label": "implemented",
        "reason": null
      },
      "arity": {
        "max": 4,
        "min": 3
      },
      "category": "Text functions",
      "classification": {
        "arg_preparation_profile": "ValuesOnlyPreAdapter",
        "coercion_lift_profile": "None",
        "determinism_class": "Deterministic",
        "error_collapse_profile": "None",
        "fec_dependency_profile": "None",
        "host_interaction_class": "None",
        "kernel_signature_class": "Custom",
        "rich_value_usage": "RichBlind",
        "surface_fec_dependency_profile": "RefOnly",
        "thread_safety_class": "SafePure",
        "volatility_class": "NonVolatile"
      },
      "descriptions": {
        "long": null,
        "short": "Text with occurrences of one text replaced by another",
        "short_source": "dna-calc-own"
      },
      "docs": {
        "english_description": "Substitutes new text for old text in a text string",
        "microsoft_url": "https://support.microsoft.com/en-us/office/substitute-function-6434944e-a904-4336-a9b0-1e58df3bc332"
      },
      "entry_kind": "built_in_function",
      "localized_family_names": null,
      "localized_names": [
        {
          "locale": "ar-SA",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "bg-BG",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "cs-CZ",
          "name": "DOSADIT"
        },
        {
          "locale": "da-DK",
          "name": "UDSKIFT"
        },
        {
          "locale": "de-DE",
          "name": "WECHSELN"
        },
        {
          "locale": "el-GR",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "en-AU",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "en-GB",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "en-US",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "es-ES",
          "name": "SUSTITUIR"
        },
        {
          "locale": "et-EE",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "fi-FI",
          "name": "VAIHDA"
        },
        {
          "locale": "fr-FR",
          "name": "SUBSTITUE"
        },
        {
          "locale": "he-IL",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "hr-HR",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "hu-HU",
          "name": "HELYETTE"
        },
        {
          "locale": "id-ID",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "it-IT",
          "name": "SOSTITUISCI"
        },
        {
          "locale": "ja-JP",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "ko-KR",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "lt-LT",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "lv-LV",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "nb-NO",
          "name": "BYTT.UT"
        },
        {
          "locale": "nl-NL",
          "name": "SUBSTITUEREN"
        },
        {
          "locale": "pl-PL",
          "name": "PODSTAW"
        },
        {
          "locale": "pt-BR",
          "name": "SUBSTITUIR"
        },
        {
          "locale": "pt-PT",
          "name": "SUBST"
        },
        {
          "locale": "ro-RO",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "ru-RU",
          "name": "ПОДСТАВИТЬ"
        },
        {
          "locale": "sk-SK",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "sl-SI",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "sr-Latn-RS",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "sv-SE",
          "name": "BYT.UT"
        },
        {
          "locale": "th-TH",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "tr-TR",
          "name": "YERİNEKOY"
        },
        {
          "locale": "uk-UA",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "vi-VN",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "zh-CN",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "zh-HK",
          "name": "SUBSTITUTE"
        },
        {
          "locale": "zh-TW",
          "name": "SUBSTITUTE"
        }
      ],
      "signature": {
        "display": "SUBSTITUTE(text, old_text, new_text, [instance_num])",
        "parameters": [
          {
            "description": null,
            "name": "text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "old_text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "new_text",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "instance_num",
            "optional": true,
            "repeats": false
          }
        ],
        "trailing_repeats": false
      },
      "surface_name": "SUBSTITUTE",
      "version_marker": null,
      "xlcall": {
        "code": "120",
        "symbol": "xlfSubstitute"
      }
    },
    "microsoft": "https://support.microsoft.com/en-us/office/substitute-function-6434944e-a904-4336-a9b0-1e58df3bc332"
  },
  "try_it": {
    "formula": "=SUBSTITUTE(\"a-b-a\",\"a\",\"x\")"
  },
  "url": "https://handbook.dnacalc.com/functions/SUBSTITUTE.html",
  "variants": [],
  "versions": []
}