4

In Chrome and Firefox (not tested in other browsers), element.style.color = rgba(0, 0, 0, 49.6%) resolves to rgba(0, 0, 0, 0.494) on my laptop. I expected rgba(0, 0, 0, 0.496).

Is this rounding behavior specified somewhere? And I can't find any related WPT.

cdoublev
  • 709
  • 6
  • 18
  • Why does it matter? unit tests? Why does your RGBA has this weird value for *opacity*? – vsync May 05 '21 at 10:12
  • Yes, for unit tests of `jsdom/cssstyle`, which is an implementation of `CSSStyleDeclaration` for JavaScript. – cdoublev May 05 '21 at 10:35
  • Consider changing the test so it doesn't care about more than the [2nd number past the floating point](https://stackoverflow.com/q/3337849/104380). (test only `0.49` or even better, round it to test for `0.5`) – vsync May 05 '21 at 11:00
  • Good idea but it won't work for the tests of the end user. `jsdom` should conform to specifications and browser behaviors. As a end user of a test environement, I would not want to expect a different value than what I can observe in browsers because my test environement is unable to serialize it as described in the specification. – cdoublev May 05 '21 at 13:30
  • You are being unreasonable. Tests should not be too harsh, especially in such scenarios like here where the human eye obviously cannot distinguish from alpha `0.49` and `0.5`. There no shame in this, but wisdom. – vsync May 05 '21 at 20:18

1 Answers1

2

From the CSSOM specification:

<alphavalue>

If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:

  1. Let alpha be the given integer.
  2. If there exists an integer between 0 and 100 inclusive that, when multiplied with 2.55 and rounded to the closest integer (rounding up if two values are equally close), equals alpha, let rounded be that integer divided by 100.
  3. Otherwise, let rounded be alpha divided by 0.255 and rounded to the closest integer (rounding up if two values are equally close), divided by 1000.
  4. Return the result of serializing rounded as a <number>. Otherwise, return the result of serializing the given value as a <number>.

Assuming that the color opacity is stored as a binary value:

const alpha = Math.round(49.6 * 2.55) // 126
const integer = Math.round(alpha / 2.55) // 49
const hasInteger = Math.round(integer * 2.55) === alpha // false
const value = hasInteger
  ? integer / 100
  : Math.round(alpha / 0.255) / 1000
// 0.494

Note on floating precision:

Some values will be problematic regarding floating precision, eg. with 50 as opacity, alpha will be rounded to 127 instead of 128, because in JavaScript (50 * 2.55) === 127.49999999999999.

A more accurate JavaScript implementation to serialize opacity would be:

const alpha = Math.round((50 * 2.55).toPrecision(15)) // 128
const integer = Math.round(alpha / 2.55) // 50
const hasInteger = Math.round((integer * 2.55).toPrecision(15)) === alpha // true
const value = hasInteger
  ? integer / 100
  : Math.round(alpha / 0.255) / 1000
// 0.5
cdoublev
  • 709
  • 6
  • 18
  • Huh, all the editor's drafts just vanished from drafts.csswg.org. That's bizarre. Hopefully just a temporary glitch. – BoltClock May 05 '21 at 07:45