{
  "category": "Operators",
  "display_name": "Text concatenation (&)",
  "entry_kind": "built_in_operator",
  "eval_api": {
    "method": "POST",
    "url": "https://handbook.dnacalc.com/api/eval"
  },
  "function_id": "FUNC.OP_CONCAT",
  "implementation_state": {
    "answer_bank": null,
    "detail": "Of 477 recorded answers (477 workbook cells), 5 do not match: 5 differ in kind or value.",
    "dnacalc_commit": "9e1884369a678158dd82d3d74188daf986995e87",
    "in_catalogue": true,
    "state": "differs",
    "state_label": "Known larger differences",
    "summary": "Known larger differences on 5 of 477 recorded answers",
    "workbook_corpus": {
      "cells": 478,
      "matched": 472,
      "other_mismatch": 5,
      "pending": 1,
      "worst_ulp": 0
    }
  },
  "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.rs#L17-L23",
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text.rs#L26-L32",
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text.rs#L34-L57"
      ],
      "repository": "https://github.com/DnaCalc/DnaCalc",
      "shared_code": [],
      "source": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/text.rs#L17-L23",
      "state": "differs",
      "suite": null,
      "summary": "Known larger differences on 5 of 477 recorded answers"
    }
  ],
  "json_url": "https://handbook.dnacalc.com/api/functions/FUNC.OP_CONCAT.json",
  "name": "OP_CONCAT",
  "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- `CONCATENATE` — the legacy function form. `data/functions/FUNC.CONCATENATE.json` places it\n  in the **Compatibility** category: Microsoft's modern replacement is `CONCAT`, and the old\n  name is retained so existing workbooks keep working. New formulas should use `CONCAT` or\n  `TEXTJOIN`; `CONCATENATE` is kept for workbook compatibility, not recommended.\n  (<https://support.microsoft.com/en-us/office/concatenate-function-8f8ae884-2ca8-4f7a-b093-75d702bea31d>)\n- `CONCAT` — the modern replacement, which accepts ranges as well as scalars.\n  (<https://support.microsoft.com/en-us/office/concat-function-9b1a9a3f-94ff-41af-9736-694cbd6b4ca2>)\n- `TEXTJOIN` — the delimiter-aware, empty-skipping join.\n  (<https://support.microsoft.com/en-us/office/textjoin-function-357b449a-ec91-49d0-80c3-0e8fc845691c>)\n- `&` is not superseded by any of them. It remains the operator; the functions are\n  alternatives with extra arguments, not replacements.\n- `TEXT`, `VALUE`, `T`, `N` — explicit conversion. If the rendering matters, `TEXT` states\n  the format instead of inheriting the general one.\n- `EXACT` — because the natural follow-up to building text with `&` is comparing it, and `=`\n  on text is case-insensitive.\n\n\n",
    "how-to-implement": "\n### Notes for implementers\n\n\n- The number-to-text rendering is the entire compatibility surface of this operator. Pin it\n  explicitly: significant digits, when scientific notation kicks in, how integers and\n  negative zero render, and what the decimal separator is. A \"close enough\" rendering\n  produces text that differs from Excel's in ways that are trivially visible to users and\n  fatal to comparisons.\n- Do not render errors. An error operand short-circuits to the error value.\n- Count the cap in UTF-16 code units, not characters and not bytes (chapter 01). Astral\n  characters count double.\n- Concatenation is associative, so chains may be evaluated in any grouping — but the\n  *rendering* of each operand must happen before joining, not after, or a numeric operand\n  could be rendered in a different context.\n\n\n",
    "quirks": "\n### Result and edge cases\n\n- **Blank operands.** Contribute the empty string.\n- **Error operands.** Propagate; `&` does not render an error as its display text. `=NA()&\"\"`\n  is `#N/A`, not `\"#N/A\"`.\n- **Arrays.** The family's broadcast rule applies, and the contract's findings show it\n  clearly: `={\"a\",\"b\"}&{\"x\";\"y\"}` produces the 2×2 outer product, and\n  `={\"a\",\"b\"}&{\"x\",\"y\",\"z\"}` produces two joined values plus `#N/A` at the coordinate the\n  left operand cannot supply. Concatenating a row against a column is therefore a compact way\n  to build a full cross-product of labels.\n- **The 32,767-unit cap.** Reachable by repeated concatenation. See chapter 01 for the\n  formula-path-versus-interop-path split, which is an observed distinction rather than a\n  documented one.\n\n\n\n### Errors\n\n`data/functions/FUNC.OP_CONCAT.json` records no Microsoft documentation URL (`docs` is\n`null`).\n\n\n",
    "sources": "- `data/presence/FUNC.OP_CONCAT.json` — implementing module\n  `earlier-implementation/operator_compare_concat_family.rs`.\n- `data/functions/FUNC.CONCATENATE.json` (category `Compatibility`),\n  `data/functions/FUNC.CONCAT.json`, `data/functions/FUNC.TEXTJOIN.json` — the function-form\n  relatives and their Microsoft URLs, quoted above.\n- Handbook `content/model/01-value-universe.md` (text as UTF-16 code units, the 32,767 cap,\n  the formula-versus-interop split), `02-coercion-and-lifting.md` (to-text outline and the\n  locale caveat), `03-call-pipeline.md`.\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`A & B` converts both operands to text and returns their concatenation, left operand first.\n\nThe whole difficulty of this operator is hidden inside \"converts to text\", because that\nconversion is a *rendering*, and rendering is where locale and formatting live:\n\n- **Numbers.** Rendered by general formatting rules (chapter 02). This is not the cell's\n  display format: a cell showing `1 234,50 €` concatenates as whatever general formatting\n  makes of the underlying number, not as what the cell shows. The decimal separator used in\n  that rendering is locale-dependent, and chapter 02 flags the locale-dependent surface as an\n  open area — so `=1.5&\"\"` is not guaranteed to be `\"1.5\"` everywhere.\n- **Logicals.** Render as the text `TRUE` and `FALSE` — in English, on the observed\n  baseline. Whether that rendering localizes is not recorded here.\n- **Dates and times.** A date is a number wearing a format (chapter 01), so `&` renders the\n  serial number, not the date. `=A1&\"\"` on a date cell gives the serial, which is the single\n  most common surprise on this operator and the reason `TEXT(A1,\"yyyy-mm-dd\")` exists.\n- **Empty.** Concatenating a blank contributes nothing, which is why `=A1&\"\"` is the standard\n  idiom for \"give me the text of this cell, or an empty string\".\n\nChapter 01's text model applies to the result: worksheet text is a sequence of UTF-16 code\nunits capped at 32,767 units, and it records that producing an over-cap string in a *formula*\npath yields `#VALUE!` where the interop path truncates. `&` is the easiest way in the whole\nfunction surface to reach that cap.\n\n\n\n### Arguments\n\n\n| Position | Name | Meaning |\n|---|---|---|\n| 0 | `A` | Left operand; rendered first. Required. |\n| 1 | `B` | Right operand; rendered second. Required. |\n\nArity is exactly 2; no optional arguments, no defaults. Order is load-bearing — concatenation\nis not commutative. A chain `a & b & c` is two applications of this two-argument operator,\nand because concatenation *is* associative, the grouping does not change the result.\n\n\n"
  },
  "signature": {
    "display": "text1 & text2",
    "parameters": [
      {
        "description": null,
        "name": "text1",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "text2",
        "optional": false,
        "repeats": false
      }
    ],
    "trailing_repeats": false
  },
  "sources": {
    "metadata": {
      "admission": {
        "label": "implemented",
        "reason": null
      },
      "arity": {
        "max": 2,
        "min": 2
      },
      "category": "Operators",
      "classification": {
        "arg_preparation_profile": "ValuesOnlyPreAdapter",
        "coercion_lift_profile": "Custom",
        "determinism_class": "Deterministic",
        "error_collapse_profile": "None",
        "fec_dependency_profile": "None",
        "host_interaction_class": "None",
        "kernel_signature_class": "TextToText",
        "rich_value_usage": "RichBlind",
        "surface_fec_dependency_profile": "RefOnly",
        "thread_safety_class": "SafePure",
        "volatility_class": "NonVolatile"
      },
      "descriptions": {
        "long": null,
        "short": null,
        "short_source": "no-short-description"
      },
      "docs": null,
      "entry_kind": "built_in_operator",
      "localized_family_names": null,
      "localized_names": [],
      "signature": {
        "display": "text1 & text2",
        "parameters": [
          {
            "description": null,
            "name": "text1",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "text2",
            "optional": false,
            "repeats": false
          }
        ],
        "trailing_repeats": false
      },
      "surface_name": "OP_CONCAT",
      "version_marker": null,
      "xlcall": null
    },
    "microsoft": null
  },
  "try_it": {
    "formula": "=\"hand\"&\"book\""
  },
  "url": "https://handbook.dnacalc.com/functions/OP_CONCAT.html",
  "variants": [],
  "versions": []
}