opa icon indicating copy to clipboard operation
opa copied to clipboard

Inconsistencies comparing int and float of same value (1 == 1.0)

Open anderseknert opened this issue 2 years ago • 8 comments

While unlikely to be a problem in real-world policies, this coercion of float-value keys in maps is pretty confusing:

% opa eval -fpretty '{1: true, 1.0: false}'
{
  "1": false
}
% opa eval -fpretty '{1.0: false, 1: true}'
{
  "1": true,
  "1.0": false
}

EDIT: Updated title to reflect that this is not isolated to object keys. More examples provided in comments below.

anderseknert avatar Jun 21 '22 08:06 anderseknert

@anderseknert Is this something you think we can resolve with better docs?

The JSON spec's section on Objects has a grammar definition that requires all keys in a JSON object to be strings, so I assume that's why the OPA implementation coerces floats to string values here-- we're trying to match the JSON spec.

philipaconrad avatar Jun 27 '22 14:06 philipaconrad

Rego has always been allowing keys to objects that aren't strings, and when the Rego rubber hits the JSON road, we need to do something about that. So we've been converting everything to strings, for better or for worse. There are known clashes, already, when using "1" as an object key while also having 1 as key -- we'll need to pick something. But for floats, this just seems particularly confusing.

But I'm not completely sure that the JSON coercion isn't just one symptom of another underlying problem. Watch this:

$ opa eval -fpretty '1.0 == 1'
true
$ opa eval -fpretty 'count({1.0, 1})'             
2

It's the same but a set consisting of both has two elements? I don't quite agree. 😅

srenatus avatar Jun 27 '22 19:06 srenatus

Indeed, it isn't the number -> string coercion that's troubling here, but how 1.0 == 1... maybe, maybe not :) The example in the first description (which Stephan provided on Slack) was really my "favorite" one, where the order of idential entries in the map determines the number of items in the output 😄

anderseknert avatar Jun 27 '22 19:06 anderseknert

Javascript, which has the same notion of 1 being equal to 1.0, does this correctly, I think:

JSON.stringify({1.0: 1})
"{\"1\":1}"
JSON.stringify({1.0: 1, 1: 1})
"{\"1\":1}"
JSON.stringify({1: 1, 1.0: 1})
"{\"1\":1}" 

anderseknert avatar Jun 27 '22 19:06 anderseknert

Perhaps a more important question to dig into: do we want to expose 2x numeric types at Rego-level (floats/ints), or do we want to expose only Javascript-style Number types that work kind of like floats most of the time?

Under the hood, we could still be switching representation dynamically, based on what's most efficient.

philipaconrad avatar Jul 08 '22 17:07 philipaconrad

do we want to expose 2x numeric types at Rego-level (floats/ints), or do we want to expose only Javascript-style Number types that work kind of like floats most of the time?

I think at least on the surface, we're (should be) trying to uphold the illusion that there's only one number type in Rego.

srenatus avatar Jul 08 '22 19:07 srenatus

Yep, like it or not, I think we'll have to accept that 1 == 1.0 😄 Let's just make it consistently so. When creating the issue I thought this was isolated to map keys, but as @srenatus have demonstrated, this is obviously not the case. I'll update the title to reflect this.

anderseknert avatar Jul 08 '22 19:07 anderseknert

This issue has been automatically marked as inactive because it has not had any activity in the last 30 days.

stale[bot] avatar Aug 07 '22 20:08 stale[bot]