10000 Rationals · Issue #23 · tc39/proposal-measure · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Rationals #23

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sffc opened this issue Feb 20, 2025 · 11 comments
Open

Rationals #23

sffc opened this issue Feb 20, 2025 · 11 comments

Comments

@sffc
Copy link
sffc commented Feb 20, 2025

Rational numbers are compatible with an opaque Amount.

The data model could be "decimal over decimal integer", where a decimal integer is the set of all Decimal128 that are integer values.

Example data models to what they represent:

  1. (2.50 / 1) inch ==> 2.50 inches
  2. (5 / 2) inch ==> 5/2 inches
  3. (5 / 2) integral-inch ==> 2 1/2 inches

This can be added on to an opaque Amount.

Alternatively, this could be a core part of the proposal, since some delegates see rationals as well-motivated, and this is a clean way to add them: arithmetic is very likely out of scope of Amount, so we remove a whole class of issues.

@jasnell
Copy link
jasnell commented Feb 21, 2025

I'm among the delegates that would see Rational as being well-motivated and would like to see it added but also think it's a bit of an uphill battle. That said, I came away from the discussion today at the plenary with a strong feeling that both Amount and a potential Rational would be better suited for 402 unless they happened to include conversion and some manner of arithmetic. Without those, they are most well suited for informational/display purposes that, at least for myself, feel better suited for 402.

It's also possible that a Rational could additionally cover a Ratio if the components are Amounts rather than just Decimals...

const a = Amount.from(...);
const b = Amount.from(...);
const r = Rational.from({ numerator: a, denominator: b });
console.log(Intl.FormatRational(r, { ... });

This would be an extremely elegant approach for a broad range of use cases.

@eemeli
Copy link
Member
eemeli commented Feb 21, 2025

I'm a bit skeptical about the use cases for rational numbers in JavaScript, and would like to see examples of real-world code that currently needs to do something un-ergonomic to work with them.

The one case I'm aware of is cooking recipes that feature US units like "⅓ cup" that want to allow for a toggle converting the units to or from metric units like "80 ml", but are there other places where rationals are currently used?

I would be particularly interested to hear of cases where the source data is a rational value, rather than the rational-ness only being an aspect of the value's formatting.

@sffc
Copy link
Author
sffc commented Feb 21, 2025

I'm a bit skeptical about the use cases for rational numbers in JavaScript, and would like to see examples of real-world code that currently needs to do something un-ergonomic to work with them.

The one case I'm aware of is cooking recipes that feature US units like "⅓ cup" that want to allow for a toggle converting the units to or from metric units like "80 ml", but are there other places where rationals are currently used?

The results of unit conversions in CLDR are represented as rationals, and ICU4X implements it using rationals.

I would be particularly interested to hear of cases where the source data is a rational value, rather than the rational-ness only being an aspect of the value's formatting.

In the same way that 1 != 1.0 for i18n concerns, 1.3333 != 4/3 != 1 1/3.

@sffc
Copy link
Author
sffc commented Feb 21, 2025

It seems likely that rationals, at least the i18n side of them, can be handled with a decimal (or even float) data model. Bear with me here:

There would be an enumeration, perhaps "RatioStyle", with the following values:

  • decimals => Do not use ratio fractions; render fractional numbers as decimals.
  • fullRatio => Render the number as a ratio.
  • partialRatio => Render the number as an integer, and only the remainder as a ratio.

Example behavior:

Input decimals fullRatio partialRatio
1.5 1.5 3/2 1 1/2
1.666667 1.666667 5/3 1 2/3
1.139782 1.139782 ? ?

With this approach, I think we also need a few more dimensions:

  1. What denominators for ratios are allowed?
    • Binary only: 1/2, 1/4, 1/8, 1/16
    • Binary with additional precision: 1/2, 1/4, 1/8, 1/16, 1/32, 1/64
    • Common naturals: 1/2, 1/3, 1/4, 1/5, 1/6, 1/8
  2. How close does the decimal need to be to the ratio?
    • +/- 0.1%?
    • Equivalence: i.e., there is a single exact Decimal128 quantity that is considered to be equivalent to "4/3"
  3. What happens with decimals that don't have a rational equivalent?
    • Fall back to decimal
    • Throw exception
    • Render as an integer over 10^N

It occurs to me that "what denominators are allowed" is context-sensitive. Thirds and sixths are things I've seen in cooking, but not in rulers or person height, for example.


I think this type of approach is a bit complex but might result in a more narrow and correct solution, at least for the i18n use case of rationals.

@jasnell
Copy link
jasnell commented Feb 21, 2025

It's also worth noting that fraction.js, just one module on npm dealing with rational numbers, sees about 17 million downloads per week and 500+ direct dependent modules. https://www.npmjs.com/package/fraction.js ... Which is just to say the use cases do exist. Whether they are enough to justify Rational is obviously up for discussion.

@sffc
Copy link
Author
sffc commented Feb 22, 2025

Following up from an interesting conversation with @michaelficarra, @mikbar-uib

I asked whether there was a fixed set of denominators for rationals that cover all non-arithmetic use cases. It seems that the ones listed above are most common (powers of 2 up to maybe 1024 and small integers less than about 10). However, there are ratios that show up in the wild that can be quite surprising, such as 30000/1001 frames per second.

To generalize to arbitrary denominators, the approach I've described about picking the canonical decimal for a floating point value (tc39/proposal-decimal#181, Lxxyx/proposal-number-is-safe-numeric#3) can also be applied to rationals. The algorithm would be, just, what is the rational with the smallest denominator that maps to the exact bucket?

I wrote some code to evaluate how often we get collisions. f64 seems to give us enough room that collisions seem rare. I wrote a little script to find them, and denominators at least up to a few thousand seem fine (until the script stops running):

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fb1e3fadab582a3caeb8ede70fb5177d

I have to make the denominator fairly large before I get a true conflict, such as 100000001/100000000 and 100000002/100000001.

@sffc
Copy link
Author
sffc commented Mar 3, 2025

I discussed this with the CLDR Design WG.

In general it seems that rational formatting could be feasible but it needs a lot of homework to be done. CLDR is not interested in doing that homework unless there is a clear user need. TC39 saying that we need this is some evidence, but ideally we have specific clients illustrating the need, especially for end users outside of en-US.

@sffc sffc moved this to Priority Issues in ECMA-402 Meeting Topics Mar 4, 2025
@srl295
Copy link
Member
srl295 commented Mar 4, 2025

rational formatting could be feasible but it needs a lot of homework to be done

I haven't seen discussion of what the format would look like outside of ASCII digits and their precomposed forms. In Tamil for example 𑿇 is 3⁄64 — That's just to give an example of the kind of homework needed. Note that I used the fraction slash U+3044, try viewing this comment in Firefox if you're not already there.

@sffc
Copy link
Author
sffc commented Mar 10, 2025

Some discussion in this CLDR issue: https://unicode-org.atlassian.net/browse/CLDR-17570

@jessealama
Copy link
Collaborator

Linking in an issue in the decimal repo where we discussed rationals.

@jessealama
Copy link
Collaborator

Another alternative data model could be, more simply, bigint over bigint rather than Decimal128 over Decimal128. This has the disadvantage that the data model is inherently unbounded, though there may be some advantages.

Some reasonable normalizations (done during construction or perhaps lazily if some operations get called) might be:

  • the numerator and denominator can't both be negative
  • only the numerator can be negative
  • both are positive; a separate sign bit is stored (though -0 probably shouldn't exist in this universe, -0/1 would get normalized to 0/1).
  • GCD

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Priority Issues
Development

No branches or pull requests

5 participants
0