3 ms·
Why would the BigDecimal implementation of 'equals' not use semantic (numeric, in this case) equality? Is 'equals' always meant to be memberwise (or perhaps mak
by happytoexplain 2y ago
Why would the BigDecimal implementation of 'equals' not use semantic (numeric, in this case) equality? Is 'equals' always meant to be memberwise (or perhaps makes performance guarantees), and if so, shouldn't there be another function/operator that is always meant to be semantic? The article doesn't acknowledge these questions.
- lmm 2y ago> Why would the BigDecimal implementation of 'equals' not use semantic (numeric, in this case) equality? A BigDecimal with different precision is not really semantically equivalent - it will lead to different results when you use it in calculations.
- happytoexplain 2y agoOh really??? That would make sense, though it raises more questions with perhaps not so brief answers.
- lmm 2y ago3 / 2 = 1, 3.0 / 2 = 1.5. So 3.equals(3.0) is false, as this "equals" is meant to have programming semantics rather than mathematical ones.
- happytoexplain 2y agoIt looks like you're comparing integer arithmetic and float arithmetic. Are you saying BigDecimal can represent both??
- lmm 2y agoThat's a special case of decimal precision. 1/3 = 0, 1.0/3=0.3, 1.00/3=0.33 is probably a better example. (where by 1.0 I mean a BigDecimal with precision=2 - in fact the default is 40)
- happytoexplain 2y agoOh, thanks, I think the phrase "programming semantics" threw me off, since I don't think of the precision behavior of ints and floats as "programming semantics", but rather "int and float semantics". In other words, it makes sense to me that integer 3/2 = 1 while float 3/2 = 1.5, because int can not represent 1.5, not because that's just how "programming" works. So, extrapolating from that logic, since BigDecimal can represent 1.5, I would expect 3/2 = 1.5 no matter how 3 and 2 are internally represented. However, what I think I'm coming to understand, is that the precision of a BigDecimal is intentionally a semantic part of the value (duh, that's what you've been saying). The only reason I can think of (as a math layman) for including the precision as a semantic part is that there are real world use cases for indicating precision and carrying it forward into results via rounding, like physical measurements. This is useful, but it also vexes me slightly because it means BigDecimal is not just for getting more precision than other numeric types, it is also for knowing/controlling the amount of precision, meaning there is no type dedicated only to the former. But I suppose the cases where you want more precision tend to also be the cases where you want to know/control the precision... It's just that this demonstrably doesn't occur to everybody using the API who might just be reaching for it because "I want more precision". I don't know how you would make it more obvious. Then again, I am essentially guessing.
- lmm 2y ago> This is useful, but it also vexes me slightly because it means BigDecimal is not just for getting more precision than other numeric types, it is also for knowing/controlling the amount of precision, meaning there is no type dedicated only to the former. But I suppose the cases where you want more precision tend to also be the cases where you want to know/control the precision... It's just that this demonstrably doesn't occur to everybody using the API who might just be reaching for it because "I want more precision". In practice someone who just "wants more precision" doesn't touch the precision and gets 40 significant figures of it, which will probably do what they want. Exact rationals have their own problems, and if you're not using exact rationals then however much precision you've got you'll always have rounding (as soon as you divide by anything other than 2 or 5).
- olliej 2y agoWhy is it not? The blog post states “BigDecimal is a class in Java used to represent floating-point numbers with arbitrary precision” Which means that the order should not semantically matter. That said even if the order necessarily does matter (infinite precision be alas unavailable), does not justify equality of different representations of the same value being incorrect. Any justification for == not comparing the represented value applies equally to compareTo (which someone else suggested?) More over operations on this type, if they can give precise/arbitrary representation should not matter if the input values are represented differently. If I perform the same operations on the same values, and in intermediate values are all representable, then the output should always be the same value. If the actual intent of this type is “equals is an invalid way to compare values” the equals should throw when the values are the same but the representation is different.
- lmm 2y ago> The blog post states “BigDecimal is a class in Java used to represent floating-point numbers with arbitrary precision” > Which means that the order should not semantically matter. Arbitrary precision in the sense that you can set an arbitrary precision on your BigDecimal which then gets carried through. 1/3 = 0, 1.0/3=0.3, 1.00/3=0.33 etc. > Any justification for == not comparing the represented value applies equally to compareTo (which someone else suggested?) No it doesn't. equals (not the same as == in Java) is meant to mean equivalent in the program, liskov-substitutable for. compareTo is meant to compare ordering. It's ok to have values that are nonequal but compare the same for ordering purposes. > If the actual intent of this type is “equals is an invalid way to compare values” the equals should throw when the values are the same but the representation is different. equals is valid, it just compares something different from the mathematical value. This is the normal behaviour in programming (equals does not understand the domain, if your program has multiple representations for things that are equivalent in the domain then those representations will should still compare unequal unless they have completely equivalent behaviour in your program)
- zeroimpl 2y ago> The blog post states “BigDecimal is a class in Java used to represent floating-point numbers with arbitrary precision” That doesn’t mean BigDecimal is used exclusively for that purpose… If you ask an engineer if 2.5 and 2.50 are the same, they will say no. It’s clear from the implementation that BigDecimal was meant for more than just representing arbitrary precision floating points, which is why there is a pitfall if that’s all you use it for. Btw that’s the reason it’s got Decimal in the class name - it’s modeling base10 strings, not numbers. If the strings are different it shouldn’t be equal.
- Igor_Wiwi 2y agoMy suggestion would be to use compareTo where possible, and rely on equals only when you know for sure that it's implemented correctly
- happytoexplain 2y ago>only when you know for sure that it's implemented correctly This is pretty disappointing advice for a standard/bundled API :(
- olliej 2y agoWhich implies the type is not compatible with the built in collections? Or have they moved aware from equals?
- bsder 2y ago"0.0 + 0.05" and "0.00 + 0.05" are not the same so "0.0" and "0.00" should probably not be considered to be the same.
- happytoexplain 2y agoWhat are the results of those two examples?
- wging 2y agoMaybe GP was thinking of the optional rounding modes? With plain old add(), you get a BigDecimal 0.05 with scale 2 in both cases and the two examples are equal according to the equals() method. var a = new BigDecimal("0.0"); var b = new BigDecimal("0.05"); var result1 = a.add(b); var c = new BigDecimal("0.00"); var result2 = c.add(b); System.out.println(result1); // 0.05 System.out.println(result2); // 0.05 System.out.println(result1.equals(result2)); // true System.out.println(result1.scale()); // 2 System.out.println(result2.scale()); // 2
- bsder 2y agoYeah, my bad, I was thinking about rounding. I'm surprised that a.add(b) promotes to an extra decimal place. That's a touch ... odd.
- wging 2y agoYou could also think of it as declining to _lose_ a decimal place; a.add(b) ought to be the same as b.add(a), so there's no reason to prefer a's precision to b. You need to take the higher precision of the two for an exact result; I assume that's the rationale. > The BigDecimal class gives its user complete control over rounding behavior. If no rounding mode is specified and the exact result cannot be represented, an exception is thrown ... - https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html https://docs.oracle.com/javase/8/docs/api/java/math/BigDecim...
- Someone 2y ago> Why would the BigDecimal implementation of 'equals' not use semantic (numeric, in this case) equality? https://stackoverflow.com/a/66180493 https://stackoverflow.com/a/66180493: “The general rule for equals is that two equal values should be substitutable for one another. That is, if performing a computation using one value gives some result, substituting an equals value into the same computation should give a result that equals the first result. This applies to objects that are values, such as String, Integer, BigDecimal, etc. Now consider BigDecimal values 2.0 and 2.00. We know they are numerically equal, and that compareTo on them returns 0. But equals returns false. Why? Here's an example where they are not substitutable: var a = new BigDecimal("2.0"); var b = new BigDecimal("2.00"); var three = new BigDecimal(3); a.divide(three, RoundingMode.HALF_UP) ==> 0.7 b.divide(three, RoundingMode.HALF_UP) ==> 0.67 The results are clearly unequal, so the value of a is not substitutable for b. Therefore, a.equals(b) should be false.”
- shagie 2y agohttps://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html https://docs.oracle.com/javase/8/docs/api/java/math/BigDecim... > Note: care should be exercised if BigDecimal objects are used as keys in a SortedMap or elements in a SortedSet since BigDecimal's natural ordering is inconsistent with equals. See Comparable, SortedMap or SortedSet for more information. https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html#equals-java.lang.Object- https://docs.oracle.com/javase/8/docs/api/java/math/BigDecim... > Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method). equals remains object comparisons. Consider the problem if `2.0` and `2.00` returned true for equals call and they were keys to a HashMap - you'd only be able to put one of them in there. https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/HashMap.java#L642 https://github.com/openjdk/jdk/blob/master/src/java.base/sha...