tc39 / proposal-intl-duration-format

https://tc39.es/proposal-intl-duration-format
MIT License
165 stars 18 forks source link

Decide if upcoming Temporal.Duration limits should have an impact on DurationFormat #157

Closed anba closed 4 months ago

anba commented 1 year ago

There are plans to limit Temporal.Duration values, details are still TBD. But we still need to decide if these limits should have an impact on Intl.DurationFormat.

The exact same issue was already discussed in #110, but that was before Temporal decided to reduce precision for their Temporal.Duration. Do we need to revisit #110?

For example I'm currently using the following BigInt-based approach to correctly implement PartitionDurationFormatPattern, steps 4.j.iv.1-3:

// Implementation for PartitionDurationFormatPattern 4.j.iv.1, similar functions are needed for 4.j.iv.2-3.
function DurationToFractionalSeconds(duration) {
  // Directly return seconds when no sub-seconds are present.
  if (
    duration.milliseconds === 0 &&
    duration.microseconds === 0 &&
    duration.nanoseconds === 0
  ) {
    return duration.seconds;
  }

  // Otherwise compute the overall amount of nanoseconds using BigInt to avoid
  // loss of precision.
  var ns_sec = NumberToBigInt(duration.seconds) * 1_000_000_000n;
  var ns_ms = NumberToBigInt(duration.milliseconds) * 1_000_000n;
  var ns_us = NumberToBigInt(duration.microseconds) * 1_000n;
  var ns = ns_sec + ns_ms + ns_us + NumberToBigInt(duration.nanoseconds);

  // Split the nanoseconds amount into seconds and sub-seconds.
  var q = ns / 1_000_000_000n;
  var r = ns % 1_000_000_000n;

  // Pad sub-seconds, without any leading negative sign, to nine digits.
  if (r < 0) {
    r = -r;
  }
  r = callFunction(String_pad_start, ToString(r), 9, "0");

  // Return seconds with fractional part as a decimal string.
  return `${q}.${r}`;
}

This approach ensures the precise results are computed for inputs like:

let df = new Intl.DurationFormat("en", {seconds: "numeric", fractionalDigits: 9});

// "10,000,000.000000002" with Number semantics, because
// 10_000_000 + (1 / 1_000_000_000) === 10000000.000000002.
assertEq(
  df.format({seconds: 10_000_000, nanoseconds: 1}),
  "10,000,000.000000001"
);

// "9,007,199,254,740,992.000000000" with Number semantics, because
// Number.MAX_SAFE_INTEGER + 2 === 9007199254740992.
assertEq(
  df.format({seconds: Number.MAX_SAFE_INTEGER, milliseconds: 2000}),
  "9,007,199,254,740,993.000000000"
);

Also see #110 and #151.

sffc commented 1 year ago

If we restricted the permissible values to integer-valued Numbers less than Number.MAX_SAFE_INTEGER, then all the math should be doable in int64 space.

Right now it looks like we have integer-valued Numbers but they can exceed MAX_SAFE_INTEGER.

sffc commented 1 year ago

@ben-allen or @ryzokuken please bring this to the floor in an upcoming TG2 call.

ben-allen commented 4 months ago

Closed by #173