{
  "category": "Operators",
  "display_name": "Equal to (=)",
  "entry_kind": "built_in_operator",
  "eval_api": {
    "method": "POST",
    "url": "https://handbook.dnacalc.com/api/eval"
  },
  "function_id": "FUNC.OP_EQUAL",
  "implementation_state": {
    "answer_bank": null,
    "detail": "Of 731 recorded answers (731 workbook cells), 6 do not match: 6 are refused.",
    "dnacalc_commit": "9e1884369a678158dd82d3d74188daf986995e87",
    "in_catalogue": true,
    "state": "differs",
    "state_label": "Known larger differences",
    "summary": "Refuses 6 of 731 recorded calls; the rest match",
    "workbook_corpus": {
      "cells": 731,
      "matched": 725,
      "refused": 6,
      "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/comparison.rs#L56-L56",
        "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/comparison.rs#L108-L113"
      ],
      "repository": "https://github.com/DnaCalc/DnaCalc",
      "shared_code": [
        "catalogue/comparison::compare",
        "catalogue/comparison::Operand::kind",
        "catalogue/comparison::Operand::blank",
        "catalogue/comparison::order",
        "catalogue/comparison::overflow_numbers",
        "catalogue/comparison::numbers",
        "catalogue/comparison::in_band",
        "catalogue/comparison::Rounded::up",
        "catalogue/comparison::Roundings::of",
        "catalogue/comparison::Roundings::disjoint",
        "catalogue/comparison::texts",
        "catalogue/comparison/collation::compare",
        "catalogue/comparison/collation::weighed",
        "catalogue/comparison/collation::measured_iteration_extension",
        "catalogue/comparison/sort_key::table",
        "catalogue/comparison/sort_key::decode64",
        "catalogue/comparison/sort_key::Reader::byte",
        "catalogue/comparison/sort_key::Reader::varint",
        "catalogue/comparison/sort_key::Reader::done",
        "catalogue/comparison/sort_key::in_ranges",
        "catalogue/comparison/sort_key::build",
        "catalogue/comparison/sort_key::han_base",
        "catalogue/comparison/sort_key::derive",
        "catalogue/comparison/sort_key::shared_jamo_tails",
        "catalogue/comparison/sort_key::ordinary_jamo_prefix",
        "catalogue/comparison/sort_key::independent_jamo_suffix",
        "catalogue/comparison/sort_key::jamo_leading_primary",
        "catalogue/comparison/sort_key::leading_order",
        "catalogue/comparison/sort_key::Before::separate_special",
        "catalogue/comparison/sort_key::Gathered::kana",
        "catalogue/comparison/sort_key::Gathered::unit",
        "catalogue/comparison/sort_key::parts",
        "catalogue/comparison/sort_key::diacritic_order",
        "catalogue/comparison/sort_key::diacritics",
        "decimal::low32",
        "decimal::Natural::from_u64",
        "decimal::Natural::pow10",
        "decimal::Natural::normalize",
        "decimal::Natural::is_zero",
        "decimal::Natural::mul_add_small",
        "decimal::Natural::times_pow10",
        "decimal::Natural::times_pow5",
        "decimal::Natural::times_pow2",
        "decimal::ExactDecimal::binary"
      ],
      "source": "https://github.com/DnaCalc/DnaCalc/blob/9e1884369a678158dd82d3d74188daf986995e87/crates/calc-func/src/catalogue/comparison.rs#L56-L56",
      "state": "differs",
      "suite": null,
      "summary": "Refuses 6 of 731 recorded calls; the rest match"
    }
  ],
  "json_url": "https://handbook.dnacalc.com/api/functions/FUNC.OP_EQUAL.json",
  "name": "OP_EQUAL",
  "notes": [
    {
      "basis": "32 recorded readings on one collation sheet.",
      "documentation": null,
      "evidence": [
        {
          "commit": "eceb809985e244e9d0a20f905e58fe57b8741ed8",
          "locator": "sheet fn3_collation, rows 290 to 321",
          "note": null,
          "path": "crates/calc-func/tests/captures/fn3_questions/oracle.capture.json",
          "repository": "DnaCalc"
        }
      ],
      "functions": [
        "FUNC.OP_EQUAL",
        "FUNC.OP_LESS_THAN"
      ],
      "inferred": null,
      "kind": "quirk",
      "note_id": "FN-OP_EQUAL-001",
      "open": [
        "Twelve further comparisons (other leading consonants, a trailing consonant, a compound vowel, compatibility jamo) were read on 2026-11-04. They are not yet in a published capture, so this note does not rest on them."
      ],
      "recorded_on": "2026-11-04",
      "repros": [
        {
          "call": "=\"가\"=\"가\"",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "FALSE",
            "kind": "logical"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZA",
          "source": {
            "commit": "eceb809985e244e9d0a20f905e58fe57b8741ed8",
            "locator": "sheet fn3_collation, row 290, column 4",
            "note": null,
            "path": "crates/calc-func/tests/captures/fn3_questions/oracle.capture.json",
            "repository": "DnaCalc"
          }
        },
        {
          "call": "=\"가\"<\"가\"",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "TRUE",
            "kind": "logical"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZA",
          "source": {
            "commit": "eceb809985e244e9d0a20f905e58fe57b8741ed8",
            "locator": "sheet fn3_collation, row 291, column 4",
            "note": null,
            "path": "crates/calc-func/tests/captures/fn3_questions/oracle.capture.json",
            "repository": "DnaCalc"
          }
        },
        {
          "call": "=\"각\"=\"각\"",
          "correct": null,
          "excel": {
            "bits": null,
            "display": "FALSE",
            "kind": "logical"
          },
          "input_route": "formula-literal",
          "inputs": [],
          "recorded_in": "Excel 16.0.20527.20088 Current Channel, Windows 64-bit (NT 10.0.26200), x86-64, AMD Ryzen 7 4800H, en-ZA",
          "source": {
            "commit": "eceb809985e244e9d0a20f905e58fe57b8741ed8",
            "locator": "sheet fn3_collation, row 296, column 4",
            "note": null,
            "path": "crates/calc-func/tests/captures/fn3_questions/oracle.capture.json",
            "repository": "DnaCalc"
          }
        }
      ],
      "schema": "efh.function-note/v1",
      "statement": "U+1100 U+1161 is canonically equivalent to the syllable U+AC00 in Unicode, but Excel's comparison says they are not equal, and the jamo sequence sorts first. The same holds with a final consonant, U+1100 U+1161 U+11A8 against U+AC01.",
      "status": "measured",
      "title": "Conjoining Hangul jamo do not compare equal to the precomposed syllable"
    }
  ],
  "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- [`FUNC.OP_NOT_EQUAL`](FUNC.OP_NOT_EQUAL.md) — the complement, with the error caveat noted\n  on that page.\n- [`FUNC.OP_LESS_THAN`](FUNC.OP_LESS_THAN.md),\n  [`FUNC.OP_LESS_EQUAL`](FUNC.OP_LESS_EQUAL.md),\n  [`FUNC.OP_GREATER_THAN`](FUNC.OP_GREATER_THAN.md),\n  [`FUNC.OP_GREATER_EQUAL`](FUNC.OP_GREATER_EQUAL.md) — the ordering siblings; same three\n  rules.\n- `EXACT` — case-sensitive text equality. Where `=` says `\"a\"=\"A\"` is true, `EXACT` says it\n  is false. If case matters, `=` is the wrong operator.\n- `IF`, `COUNTIF`, `SUMIF`, `SUMIFS`, `MATCH`, `XLOOKUP` — the consumers. Note that the\n  criteria *strings* of the `*IF*` family (`\"=5\"`, `\">3\"`, `\"<>\"`) are a separate\n  mini-language parsed by those functions; they are not this operator, and their matching\n  rules — wildcards, in particular — do not follow the rules on this page.\n- `DELTA` — the engineering-function equivalent for numbers, returning 1 or 0.\n\n\n",
    "how-to-implement": "\n### Notes for implementers\n\n\n- Do not implement `=` as `a == b` on doubles, and do not implement it as a type-coercing\n  comparison. Both are different functions from the one described here.\n- Implement the kind ordering as an explicit total order over kinds, then compare within\n  kind. That structure makes rules 1 and 2 separable and testable.\n- Put the numeric normalization in one place shared by all six comparisons. If `=` and `<`\n  normalize differently, the operators stop being consistent with each other — and a reader\n  can detect that with three cells.\n- Case-insensitive comparison is not `to_lowercase` in general. For ASCII it is; for Unicode,\n  case folding has locale-sensitive edges (Turkish dotless i is the standard example), and\n  the contract puts that surface out of slice — so a compatibility implementation should\n  record what it does rather than assume.\n- Comparison is `SafePure`, `Deterministic`, `NonVolatile`.\n\n\n",
    "quirks": "\n### Result and edge cases\n\n- **Blank operands.** The contract records blank comparison as context-sensitive, with the\n  blank taking the shape of the other operand: blank vs number behaves as `0`, blank vs text\n  as `\"\"`, blank vs logical as `FALSE`. Its baseline findings include a blank cell comparing\n  equal to `0` and a blank cell comparing equal to `\"\"` — which together mean `=A1=0` and\n  `=A1=\"\"` can both be `TRUE` for the same empty `A1`, even though `0` and `\"\"` are not equal\n  to each other. This is not a contradiction; it is what context-sensitive blank coercion\n  means, and it is a genuine trap.\n- **Empty text versus blank.** `\"\"` is a text value of length zero; a blank cell is the\n  `Empty` kind (chapter 01). They are different values that can compare equal to the same\n  things.\n- **Arrays.** The contract records the ordinary broadcast rule: singleton dimensions\n  broadcast, row-vs-column combinations produce two-dimensional grids, and coordinates\n  neither operand can supply return `#N/A`. Its findings include `={1,2}={1;2}` producing a\n  2×2 grid of logicals.\n\n\n\n### Errors\n\n\n| Error | Condition |\n|---|---|\n| any incoming error | Propagates unchanged. Comparison has no error-collapse profile of its own. |\n| `#N/A` | Inside an array result only: a coordinate neither operand supplies. |\n\n",
    "sources": "- `data/presence/FUNC.OP_EQUAL.json` — implementing module\n  `earlier-implementation/operator_compare_concat_family.rs`, the family slug for\n  this page.\n- Handbook `content/model/01-value-universe.md` (value kinds, Empty versus text),\n  `02-coercion-and-lifting.md` (why arithmetic coerces text and comparison does not),\n  `03-call-pipeline.md` (operators are functions; parse-only syntax is not).\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` returns the logical `TRUE` or `FALSE` according to whether the two operands are equal\nunder Excel's comparison rules — which are not the rules of IEEE-754, not the rules of\nordinary string equality, and not the rules of a type-strict language.\n\nThree rules do all the work. They are shared by all six comparison operators, and this page\nis where they are stated in full.\n\n### Rule 1 — cross-type ordering, not cross-type coercion\n\n```\n   any Number   <   any Text   <   any Logical\n```\n\nSo the number 10 is less than the text `\"2\"`, and `FALSE` is greater than any number. Under\nthis rule `A = B` between operands of different kinds is always `FALSE`, no matter how\nconvertible they look. This is the single most important thing on the page: `1 = \"1\"` is\nfalse, and every \"why doesn't my lookup match?\" question in a spreadsheet forum eventually\narrives here.\n\nNote the contrast with arithmetic: [`+`](FUNC.OP_ADD.md) *does* coerce `\"1\"` to 1. Comparison\nand arithmetic have opposite doctrines about text, in the same formula language.\n\n### Rule 2 — text comparison is case-insensitive\n\nThe contract records comparison text lanes as case-insensitive on the admitted slice, with\n`=\"a\"=\"A\"` yielding `TRUE`. `=` is therefore not string identity. `EXACT` is the\ncase-sensitive alternative.\n\nBeyond case, text comparison raises collation questions — accents, ligatures, non-Latin\nscripts, locale-specific ordering — which the contract explicitly puts out of slice (\"locale\nand collation-sensitive text ordering beyond the current installed baseline\"). Apart from the\nHangul jamo pairs in FN-OP_EQUAL-001, nothing about non-ASCII text ordering is settled here.\n\n### Rule 3 — numeric equality is normalized, not exact\n\nThe rule that surprises engineers most. The contract records that numeric-vs-numeric\ncomparisons on the admitted operator slice do **not** use exact IEEE-double equality, but a\ncurrently pinned normalization lane, with the current local model described as\n\"truncation-style normalization to 15 significant decimal digits on the tested compare\npaths, not round-to-nearest\". Its baseline finding is the classic one: `=0.1+0.2=0.3` yields\n`TRUE`, together with the matching results for the other five comparisons.\n\nThis is why a spreadsheet appears not to suffer from floating-point equality problems while\nthe arithmetic underneath it plainly does. The comparison layer hides the residue that the\narithmetic layer creates.\n\nTwo consequences worth spelling out:\n\n- **Equality here is not the same relation as bitwise equality.** Two distinct doubles can\n  compare equal. Any implementation that reaches for `a == b` on doubles has implemented a\n  different function.\n- **Whether the relation is transitive depends on which model is right.** A *normalization*\n  model (round or truncate each operand to 15 significant digits, then compare exactly) is\n  transitive: it is equality of a function of the operands. A *tolerance* model (compare the\n  difference against a scaled epsilon) is not transitive: `a≈b` and `b≈c` need not give\n  `a≈c`. The contract names the normalization model as its current local one and marks the\n  whole lane as currently pinned rather than settled — so transitivity of `=` on numbers is\n  an open question with a decisive experiment behind it.\n\n### The other `=`\n\nThe `=` a reader types first is not this function. A leading `=` introduces a formula: it is\ngrammar, telling the host that what follows is an expression rather than literal content.\nThis operator is the `=` that appears *inside* an expression, between two subexpressions.\n`=A1=B1` contains both: the first `=` is the formula marker, the second is\n`FUNC.OP_EQUAL`. Chapter 03 draws exactly this line — parse-only syntax carries no semantics\nand gets no function identity, while every evaluable operator does.\n\n\n\n### Arguments\n\n\n| Position | Name | Meaning |\n|---|---|---|\n| 0 | `A` | Left operand. Required. |\n| 1 | `B` | Right operand. Required. |\n\nArity is exactly 2; no optional arguments, no defaults. Equality is symmetric, so operand\norder does not change the value — but it does set the broadcast orientation when the operands\nare arrays of different shapes.\n\n\n"
  },
  "signature": {
    "display": "value1 = value2",
    "parameters": [
      {
        "description": null,
        "name": "value1",
        "optional": false,
        "repeats": false
      },
      {
        "description": null,
        "name": "value2",
        "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": "Custom",
        "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": "value1 = value2",
        "parameters": [
          {
            "description": null,
            "name": "value1",
            "optional": false,
            "repeats": false
          },
          {
            "description": null,
            "name": "value2",
            "optional": false,
            "repeats": false
          }
        ],
        "trailing_repeats": false
      },
      "surface_name": "OP_EQUAL",
      "version_marker": null,
      "xlcall": null
    },
    "microsoft": null
  },
  "try_it": {
    "formula": "=1=1"
  },
  "url": "https://handbook.dnacalc.com/functions/OP_EQUAL.html",
  "variants": [],
  "versions": []
}