scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Match types do not treat 0.0d and -0.0d as equivalent constants

Open kijuky opened this issue 6 months ago • 5 comments

Compiler version

Scala 3.7.0 (Confirmed with both sbt and Scala CLI)

Minimized code

type DoubleToString[D <: Double] <: String = D match
  case 0.0 => "0.0"
  case _ => "_"

@main def main(): Unit =
  summon[DoubleToString[0.0] =:= "0.0"]
  summon[DoubleToString[-0.0] =:= "0.0"]
  summon[DoubleToString[-0.0] =:= "_"]

Compilation output

[error] -- [E172] Type Error: src/main/scala/Main.scala:7:40 
[error] 7 |  summon[DoubleToString[-0.0] =:= "0.0"]
[error]   |                                        ^
[error]   |  Cannot prove that DoubleToString[(-0.0d : Double)] =:= ("0.0" : String).
[error]   |
[error]   |  Note: a match type could not be fully reduced:
[error]   |    trying to reduce  DoubleToString[(-0.0d : Double)]
[error]   |    failed since selector (-0.0d : Double)
[error]   |    is uninhabited (there are no values of that type).

[error] -- [E172] Type Error: src/main/scala/Main.scala:8:38 
[error] 8 |  summon[DoubleToString[-0.0] =:= "_"]
[error]   |                                      ^
[error]   |  Cannot prove that DoubleToString[(-0.0d : Double)] =:= ("_" : String).
[error]   |  Note: a match type could not be fully reduced:
[error]   |    trying to reduce  DoubleToString[(-0.0d : Double)]
[error]   |    failed since selector (-0.0d : Double)
[error]   |    is uninhabited (there are no values of that type).

Problem description

In Scala 3.7.0, match types cannot distinguish or unify 0.0 and -0.0, even though:

  • 0.0d == -0.0d holds at runtime
  • Using =:= on the type level compiles successfully: summon[0.0 =:= -0.0] works

However, in match types, writing case 0.0 => causes -0.0 to be rejected as an uninhabited selector. Switching to case -0.0 => then rejects 0.0.

This suggests that match types are not treating 0.0d and -0.0d as equivalent constant values, which is inconsistent with how =:= treats them.

Expected behavior

Match types on Double constants should not distinguish 0.0 and -0.0, just as =:= does not.

kijuky avatar May 25 '25 08:05 kijuky

  • Using =:= on the type level compiles successfully: summon[0.0 =:= -0.0] works

Huh. To me, that is the real bug. +0.0 and -0.0 definitely are not the same value. They should not have the same literal type. For example, literal types can cause constant-folding. You don't want to constant-fold -0.0 into +0.0, since it can change the result of computations.

sjrd avatar May 25 '25 09:05 sjrd

+0.0 and -0.0 definitely are not the same value.

If it is a value (not a type), the comparison is a match - this appears to be a JVM specification.

println(0.0 == -0.0) // => true

https://docs.oracle.com/javase/specs/jvms/se24/html/jvms-2.html#:~:text=Positive%20zero%20and%20negative%20zero%20compare%20equal

Since 0.0 and -0.0 as values ​​are indistinguishable, I think it would be better if literal types did not distinguish between 0.0 and -0.0 either. My expectation is that comparisons and operations on literal types work the same as on values.

kijuky avatar May 25 '25 10:05 kijuky

+0.0 and -0.0 are definitely not indistinguishable. You can tell them apart with 1.0/x, for example. The fact that they compare == does not mean they are the same value or that are indistinguishable.

Conversely, NaN == NaN is false, but NaN is the same value as NaN.

In general, == cannot be trusted to answer the question "are the operands the same value/indistinguishable?".

sjrd avatar May 25 '25 11:05 sjrd

As for 0, since values ​​do not distinguish between positive and negative, I thought it would be better to not distinguish between positive and negative in literal types as well, so that operations could be related to values ​​and types. In fact, positive and negative are not distinguished outside of match types.

However, does this mean that it is better to distinguish between 0.0 and -0.0 for literal types?

(Personally, regardless of the conclusion, I would like the behavior of match types to be the same as others. In other words, if we go with the current situation, I would not like the distinction between positive and negative. If we go with your proposal, I would like the distinction to be made in behavior other than match types, and I would like to be able to put positive and negative zeros in case clauses.)

kijuky avatar May 25 '25 11:05 kijuky

However, does this mean that it is better to distinguish between 0.0 and -0.0 for literal types?

Yes, that's what I meant in my first comment.

sjrd avatar May 25 '25 11:05 sjrd