(Goal A) Mathesar is capable of displaying and accepting entry for all numbers which Postgres is capable of storing.
(Goal B) Aside from the numbers,
Infinity, users are able to type all numbers into Mathesar exactly as Mathesar displays those numbers to them.
(Goal C) When a user enters data which cannot be accurately stored in the column exactly as-entered, then Mathesar stores the nearest acceptable value and clearly communicates to stored value to the user.
For example, a
float8 column cannot accurately store the value
9999999999999999 (because it will instead be stored as
1e+16). In this example, Mathesar will store
1e+16, displaying that value back to the user after entry.
(Goal D) Mathesar displays numbers and accepts entry of numbers in the user’s desired locale, including the correct decimal separator and grouping separators.
(Goal E) Mathesar auto-formats numbers as the user types.
(Goal F) Mathesar accepts entry of numbers in scientific notation and displays numbers in scientific notation when the user is likely to prefer it. For example
1E100 instead of 1 followed by 100 zeros.
Throughout our stack, we may want to represent numbers as strings. When doing so, we follow a format very similar to numbers in JSON.
The decimal separator is an ASCII dot.
The minus sign is an ASCII hyphen.
The exponent separator is an uppercase “E”.
Grouping separators are disallowed.
"Infinity" are used to represent special Postgres numbers. Note that this is one way in which our stringified number format differs from JSON – raw values like
NaN are not valid inside JSON.
"NULL" should not be used. In JSON, the value
null should be used instead.
To satisfy Goal D, Number columns have an optional
locale display option which is set by the front end and read by the front end. If a column’s
display_options does not have a
locale value, then the front end infers the locale from the user’s browser. The back-end remains locale-agnostic and never uses the
locale display option for any back-end logic.
Mathesar is limited to only displaying and accepting entry for numbers using the latin numbering system. This means sacrificing Goal A for the locales “bn”, “fa”, and “mr” (Bengali, Persian, Marathi) which use non-latin numbering systems by default.
The minus sign is always displayed using an ASCII hyphen, regardless of locale (even though some locales like ‘li’ use the Unicode minus sign). The Unicode minus sign (and similar characters) will be accepted in all locales while entering numbers though.
For display of scientific notation, the exponent separator is always displayed using the ASCII “E” regardless of locale even though some locales use other exponent separators. For input, other strings are accepted too, such as “e” (lowercase) and “Е” (U+0415) and variations of “x10^”.
The front end parses user input, by performing string transformations to normalize the input into canonical stringified form. For example, all characters matching the current locale’s decimal separator are converted to ASCII dots.
The front end parses canonical stringified numbers (as produced above, or received from the API) by attempting to produce a
BigInt which can be re-formatted exactly to match the input (without loss of precision).
The front end will fail to parse canonical stringified numbers like
The front end actually almost doesn’t need to parse numbers at all. But it does so for the following reasons.
Because we want to format a cell’s numerical value for display. There are a variety of smaller goals here.
IntlAPI takes care of it for us, but only if we have a
BigInt– so that requires parsing.
Intloffers a bunch of other formatting options that we’ll likely want to utilize at some point in the future, like
maximumFractionDigits. That stuff is trivial to implement when using
Intl, but harder to do with raw strings.
Because we also want to accept user-entered number in other contexts, besides the cell values
For editing a cell, we’ll likely never need access to the numerical value of the cell on the front end (beyond the need to format the number for display). But we will need access to numerical values of user input on the front end in the future. For example, when/if we allow the user to configure the minimum number of fraction digits, I’d want to take that user input and feed it back into
Intl.NumberFormat to display a formatted preview of a sample number. I think we’d also want client-side validation to ensure that
minimumFractionDigits is within
20, as required by
The front-end formats numbers for display in the browser’s locale.
Values from the API are formatted (to satisfy Goal D), and values from the user are formatted as the user types (to satisfy Goal E). In both of these cases we begin with a string.
When the front end can parse the input into a
BigInt, it uses
Intl.NumberFormat for formatting, which allows robust handling of grouping separators across various locales.
When the front end cannot parse the canonical stringified number, it falls back to a simplistic formatter which only performs string transformation to ensure only that the decimal separator matches the user’s locale. This produces a formatted result which lacks grouping separators, partially sacrificing Goal D.
The front-end allows users to enter the Postgres numbers
Infinity using a context menu – and it displays those numbers stylized (similar to
While the user is entering a number, Mathesar partially reformats the user’s entry in order to match the final display of the number as closely as possible (to satisfy Goal E). For example, with a “en-US” locale, the entry
1234 is reformatted to
1,234 as the user types. However, some entries cannot be fully reformatted as the user types. For example, an entry of
1. would be fully formatted to
1 but must preserve the trailing decimal so that the user may type additional characters.
After the user shifts focus away from the number input, Mathesar fully reformats their entry. For example,
1. will be reformatted to
1 (removing the trailing zero).
To satisfy Goal F, Mathesar delegates to the front-end the responsibility to decide between displaying a number in standard notation or scientific notation.
The algorithm for making this decision is as follows.
BigInt, then it will use standard notation because: (a)
Intl.NumberFormatis not capable of accurately formatting very large
BigIntvalues due to its upper limit on
20, and (b) standard notation makes it clearer to the user that the value is an integer.
number, then it will use some yet-to-be-determined heuristics to make a best guess as to whether the user would prefer to see the number in standard notation or scientific notation.
psql, spreadsheets, and AirTable.)