Skip to content

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

@kijuky

Description

@kijuky
Contributor

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.

Activity

sjrd

sjrd commented on May 25, 2025

@sjrd
Member
  • 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.

kijuky

kijuky commented on May 25, 2025

@kijuky
ContributorAuthor

+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.

sjrd

sjrd commented on May 25, 2025

@sjrd
Member

+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?".

kijuky

kijuky commented on May 25, 2025

@kijuky
ContributorAuthor

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.)

sjrd

sjrd commented on May 25, 2025

@sjrd
Member

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.

added a commit that references this issue on May 25, 2025
40689d1
added a commit that references this issue on May 26, 2025
4ab5ca6
removed
stat:needs triageEvery issue needs to have an "area" and "itype" label
on May 29, 2025
added a commit that references this issue on Jul 13, 2025
cfe430d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @kijuky@xuwei-k@sjrd@Gedochao

      Issue actions

        Match types do not treat 0.0d and -0.0d as equivalent constants · Issue #23261 · scala/scala3