# A postal code is not a negative number Source: https://truecopy.dev/blog/a-postal-code-is-not-a-negative-number/ truecopy 2.0.14 lets the caller say what a bracket around a figure means. Why the library stopped guessing, and why the default did not move. On a balance sheet, `(1 234)` is a loss. On an address line, `(69003)` is a postal code. A footnote marker `(1)` has the same shape again. Until 2.0.14, truecopy read all three the first way, and the reason it took that long to notice says something about where reading errors hide. ## It showed up as a refused citation Nobody saw a wrong total. What happened is quieter. truecopy has a check for the moment a model claims a figure came from a page: [`carriesNumber(source, value)`](https://truecopy.dev/docs/cite/) answers whether the page really prints that figure. A caller cited a postal code exactly as the page wrote it and was told the page does not carry it. The reader had turned `(69003)` into -69003, and +69003 is not -69003. [`carriesText`](https://truecopy.dev/docs/cite/) refused it too, for another reason. It matches whole words, and `(69003)` was one word. That is the worst way for this check to fail. It exists to catch a model that invents a figure. Here it accused a citation that was right. A check that rejects correct answers gets switched off by the people it annoys, and then it catches nothing at all. ## Why the library cannot tell the two apart The obvious fix is a rule. Look at the position of the bracket, or at what comes before it, and decide. The issue that reported the defect measured it before anyone wrote that rule. On 270 quarterly reports, 2 637 of 4 176 bracketed figures sat at the head of a cell. That is exactly where an accounting negative sits too, so position settles nothing. What comes before the bracket separates `avenue des Lilas (69003)` from a column of figures, but a footnote marker has nothing before it. There was a worse problem with tuning a rule on that set. Those reports print asides in brackets and almost never print negatives that way. The documents this library is written for, statements, invoices and payslips, are where accounting negatives are expected to live. A rule fitted to the first kind of page would have been fitted to the one place the negatives are missing, and it would have flipped signs on the second kind without a word. The token does not carry its convention. The page does, and the caller knows which page it holds. ## The caller says it, the way it already says the decimal mark truecopy had met this shape of problem before. `1,234` is one thousand two hundred and thirty-four in one country and a little over one in another, and no amount of staring at the string decides it. So the caller declares the decimal mark, and [`decimalMarkOf`](https://truecopy.dev/docs/notation/) helps it read the answer from the rest of the document. Brackets now work the same way. `readNumber`, `carriesNumber` and `carriesText` take one more argument, `'sign'` or `'punctuation'`: ```js import { readNumber, carriesNumber, carriesText } from 'truecopy'; readNumber('(69003)'); // -69003 readNumber('(69003)', null, 'punctuation'); // 69003 const line = '14 avenue des Lilas (69003) Lyon'; carriesNumber(line, '69003', ','); // false carriesNumber(line, '69003', ',', 'punctuation'); // true carriesText(line, '69003', 'punctuation'); // true ``` A minus sign stays a minus sign under both readings, so `(-12,00)` is -12 whichever convention the caller declares. ## Why the default did not move Left out, the argument is `'sign'`, which is what every earlier version did. Changing the default would have fixed the address lines and broken every accounts table read by an existing caller, without a warning anywhere. This is what `'punctuation'` does to a real accounting negative: ```js readNumber('(1 234,56)', ','); // -1234.56 readNumber('(1 234,56)', ',', 'punctuation'); // 1234.56 ``` A loss that comes back as a profit is the error this library exists to prevent. A visible refusal on an address line is annoying. A silent change of sign on a balance sheet is the kind of mistake that reaches a decision before anyone sees it. So the new reading arrives as an option, and a bracket that was read as a sign before 2.0.14 is still read as a sign after it. ## If your documents hold both Declare the convention for the text you are reading, not once for the whole program. `readNumber` reads one string at a time, so a document with an address block and a column of amounts can be read with `'punctuation'` for the first and `'sign'` for the second. The library will not decide that for you, because it cannot know it from the page. The full reference is on the [notation page](https://truecopy.dev/docs/notation/) and the [citation page](https://truecopy.dev/docs/cite/), and the release notes are on [the versions page](https://truecopy.dev/versions/). The report that started it is [issue #70](https://github.com/truecopydev/truecopy/issues/70). Map of this site for a model: https://truecopy.dev/llms.txt Every page in one file: https://truecopy.dev/llms-full.txt