5 ms·
People try to get cute with 3-way compares. A real Java bug that inspired: https://errorprone.info/bugpattern/BadComparable https://errorprone.info/bugpattern/B
by irogers 4y ago
People try to get cute with 3-way compares. A real Java bug that inspired:
https://errorprone.info/bugpattern/BadComparable https://errorprone.info/bugpattern/BadComparable
public MyFile implements Comparable {
...
long timestamp;
...
@Override
public int compare(Object other) {
return (int)(((MyFile)other).timestamp - timestamp;
}
...
}
The int conversion loses the sign of the subtract. The particular use of the code was sorting files to delete the X number of oldest.
More examples about the dangers with just ints:
https://stackoverflow.com/questions/2728793/java-integer-compareto-why-use-comparison-vs-subtraction https://stackoverflow.com/questions/2728793/java-integer-com...
The comparable API in Java came from the C qsort API. It would have been better to just have a less-than method. Kind of surprised to see this in Go near a core API.
- Dylan16807 4y agoI don't blame the cute three way comparison at all for that problem. Converting number types incorrectly is an endemic problem, and using the right conversion would have made this work quite nicely.
- scotty79 4y agoWhat's the right conversion in this case? How to narrow down negative long to negative int without loosing a sign?
- slaymaker1907 4y agoIt could or for integer types less than 4 bytes. For integers, while I don't think Java has this built in, you could use saturating subtraction (so it goes to INT_MIN or something on underflow).
- Dylan16807 4y agoFor this situation you want it to saturate. Clamp the value between int_min and int_max.
- wiml 4y agoI don't know about Java or Go, but any time I've looked at a C compiler's output it's emitted pretty good assembly for "if (a>b) return 1; else if (a<b) return -1; else return 0;" kinds of input when optimization is on. As long as the type being compared is something the compiler can reason about (and/or has a peephole optimization for I guess).
- sltkr 4y agoYou could call Long.signum(), but that's not the right solution here: you should not subtract the values, but call Long.compare(a, b) directly, which is clear, efficient and correct. The reason to avoid subtraction is that you can get integer underflow if one operand is negative and one is positive, and then the result of Long.signum(b - a) is different from Long.compare(a, b).
- masklinn 4y agoBut of course the real solution is to use a strong and composable type for your comparison result, rather than remove the entire thing because you originally designed the API wrong.
- tialaramex 4y agoIndeed. 1) Get strong types 2) Make a type representing Ordering 3) Use this type everywhere for ordering things. Stop worrying about it. Using -1, 0 and 1 to represent these feels like something that was a clever trick for low level code on a PDP-11 and hasn't been a good idea since the 1970s.
- romac 4y agoI like how Rust does it with the `Ordering` type and associated combinators: https://doc.rust-lang.org/stable/std/cmp/enum.Ordering.html https://doc.rust-lang.org/stable/std/cmp/enum.Ordering.html For two values `x` and `y` of the same type with fields `a` and `b`, this lets you compare on `a` first and then on `b` by writing ``` x.a.cmp(&y.a).then(x.b.cmp(&y.b)) ```
- masklinn 4y agoIn fairness that was directly inspired by Haskell’s type of the same name: https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelude.html#t:Ordering https://hackage.haskell.org/package/base-4.17.0.0/docs/Prelu...
- im3w1l 4y agoNot bad but I think python's way - using tuples is cleaner (x.a, x.b) < (y.a, y.b) btw you can format as code by indenting two spaces.