Why HashSet differentiates 0.0 and -0.0 - java

When I tried this:
HashSet<Double> set = new HashSet<>();
Double d1 = new Double(0);
Double d2 = new Double(0);
Double d3 = new Double(-0);
set.add(d1);
System.out.println(set.contains(d2));
System.out.println(set.contains(d3));
The output was what I expected:
true
true
But when I tried:
HashSet<Double> set = new HashSet<>();
Double d1 = new Double(0.0);
Double d2 = new Double(0.0);
Double d3 = new Double(-0.0);
set.add(d1);
System.out.println(set.contains(d2));
System.out.println(set.contains(d3));
or
set.add(Double.valueOf(d1));
System.out.println(set.contains(Double.valueOf(d2)));
System.out.println(set.contains(Double.valueOf(d3)));
To my surprise, the output was:
true
false
Why this happened? How do I make HashSet treat (0.0) and (-0.0) the same?
Is there a better way than if(num == -0.0) num = 0.0;?

This is explained by the docs for Double.
If d1 represents +0.0 while d2 represents -0.0, or vice versa, the equal test has the value false, even though +0.0==-0.0 has the value true.
So a Double created from 0.0 is not the same as a Double created from -0.0. The same is not true when you use 0 and -0 because integers use twos-complement, which has no notion of negative zero. -0 is the same as 0. doubles, on the other hand, use the IEEE standard for floating point values, which does recognize a negative zero value.
This behavior is all fixed, so there's no way to have a HashSet treat 0.0 and -0.0 as the same. If you want to do that, you'll need to manually convert all negative zero values into positive zeros before adding or searching for them.

-0.0 is a literal for a double value that is distinct from 0.0.
-0 is the negation operator applied to the int value 0, which gives just the int value 0.
Therefore, new Double(-0) is equivalent to new Double(0), whereas new Double(-0.0) and new Double(0.0) actually produce two non-equal Double objects.
See this question for some explanations for why it is necessary to have two different floating point zero values.

As explained in Wikipedia (thanks), the IEEE format for floating point actually supports negative and positive zeros for various reasons. https://en.wikipedia.org/wiki/Signed_zero
The reason why you find the 0.0 and -0.0 different in your hashmap is directly derived from the IEEE representation. The Double#hashCode method uses the raw bits of the floating point number to calculate the hashcode. Since 0.0 and -0.0 and possibly even +0.0 are different in terms of bits, as some numerical calculations obviously require this, their bits are different and therefore the hashcodes.

Related

BigDecimal to values monetary

I have two float values:
float value1 = 1.9f;
float value2 = 20;
I want to multiply them and get an exact result, so I use BigDecimal and expect 38 as result:
BigDecimal total = new BigDecimal(value1).multiply(new BigDecimal(value2));
System.out.println(total); // 37.99999952316284179687500
When I do the same test with 10 and 1.9, I get 18.99999976158142089843750 instead of 19.
Why do I lose precision?
This is explained in the javadoc for the BigDecimal(double) constructor:
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
And although your variables are floats, there is not ctor that takes floats, so they are cast as doubles. And like the docs say, if you used Strings for your values, you would get the exact result (38) you expect.
The error is right at the start:
float value1 = 1.9f;
You may think that value1 now contains exactly the value 1.9. But this is not the case. Floating point values are stored in binary. The important thing to remember is that some real values cannot be expressed by a finite sequence of digits, such as a float. 1.9 is such a number (just like in decimal the value 1.333... cannot be expressed by a finite sequence of digits).
So you should use BigDecimal from the start. Then the values can be exactly represented (because it is not stored in binary but in decimal) and the calculation results in the expected answer.
Let's say your value1 is a double as primitive type and want to output 2 decimal places and the value1 is 350. 4432567
double roundTotal = Math.round(value1 * 100.0) / 100.0;
or
double roundTotal = (double) Math.round(value1 * 100) / 100;
Output:
350.44
Note that the 2 digits precision. Zeros indicate the wanted number of decimal to display.
Example #2
double roundTotal = (double) Math.round(value1 * 10000) / 10000;
Output:
350.4432
You can use
Math.floor(float f);
//or
Math.ceil(float f);
functions for the exact value. Or you can override these functions for BigDecimal class.

Java signed zero and boxing

Lately I've written a project in Java and noticed a very strange feature with double/Double implementation. The double type in Java has two 0's, i.e. 0.0 and -0.0 (signed zero's). The strange thing is that:
0.0 == -0.0
evaluates to true, but:
new Double(0.0).equals(new Double(-0.0))
evaluates to false. Does anyone know the reason behind this?
It is all explained in the javadoc:
Note that in most cases, for two instances of class Double, d1 and d2, the value of d1.equals(d2) is true if and only if
d1.doubleValue() == d2.doubleValue()
also has the value true. However, there are two exceptions:
If d1 and d2 both represent Double.NaN, then the equals method returns true, even though Double.NaN==Double.NaN has the value false.
If d1 represents +0.0 while d2 represents -0.0, or vice versa, the equal test has the value false, even though +0.0==-0.0 has the value true.
This definition allows hash tables to operate properly.
Now you might ask why 0.0 == -0.0 is true. In fact they are not strictly identical. For example:
Double.doubleToRawLongBits(0.0) == Double.doubleToRawLongBits(-0.0); //false
is false. However, the JLS requires ("in accordance with the rules of the IEEE 754 standard") that:
Positive zero and negative zero are considered equal.
hence 0.0 == -0.0 is true.
It important to undertand the use of signed zero in the Double class. (Loads of experienced Java programmers don't).
The short answer is that (by definition) "-0.0 is less than 0.0" in all the methods provided by the Double class (that is, equals(), compare(), compareTo(), etc)
Double allows all floating point numbers to be "totally ordered on a number line".
Primitives behave the way a user will think of things (a real world definition) ... 0d = -0d
The following snippets illustrate the behaviour ...
final double d1 = 0d, d2 = -0d;
System.out.println(d1 == d2); //prints ... true
System.out.println(d1 < d2); //prints ... false
System.out.println(d2 < d1); //prints ... false
System.out.println(Double.compare(d1, d2)); //prints ... 1
System.out.println(Double.compare(d2, d1)); //prints ... -1
There are other posts that are relevant and nicely explain the background ...
1: Why do floating-point numbers have signed zeros?
2: Why is Java's Double.compare(double, double) implemented the way it is?
And a word of caution ...
If you don't know that, in the Double class, "-0.0 is less than 0.0", you may get caught out when using methods like equals() and compare() and compareTo() from Double in logic tests. For example, look at ...
final double d3 = -0d; // try this code with d3 = 0d; for comparison
if (d3 < 0d) {
System.out.println("Pay 1 million pounds penalty");
} else {
System.out.println("Good things happen"); // this line prints
}
if (Double.compare(d3, 0d) < 0) { //use Double.compare(d3, -0d) to match the above behaviour
System.out.println("Pay 1 million pounds penalty"); // this line prints
} else {
System.out.println("Good things happen");
}
and for equals you might try ... new Double(d3).equals(0d) || new Double(d3).equals(-0d)
By using == statement you are comparing values. With equals your are comparing objects.

Convert double to BigDecimal and set BigDecimal Precision

In Java, I want to take a double value and convert it to a BigDecimal and print out its String value to a certain precision.
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
double d=-.00012;
System.out.println(d+""); //This prints -1.2E-4
double c=47.48000;
BigDecimal b = new BigDecimal(c);
System.out.println(b.toString());
//This prints 47.47999999999999687361196265555918216705322265625
}
}
It prints this huge thing:
47.47999999999999687361196265555918216705322265625
and not
47.48
The reason I'm doing the BigDecimal conversion is sometimes the double value will contain a lot of decimal places (i.e. -.000012) and the when converting the double to a String will produce scientific notation -1.2E-4. I want to store the String value in non-scientific notation.
I want to have BigDecimal always have two units of precision like this: "47.48". Can BigDecimal restrict precision on conversion to string?
The reason of such behaviour is that the string that is printed is the exact value - probably not what you expected, but that's the real value stored in memory - it's just a limitation of floating point representation.
According to javadoc, BigDecimal(double val) constructor behaviour can be unexpected if you don't take into consideration this limitation:
The results of this constructor can be somewhat unpredictable. One
might assume that writing new BigDecimal(0.1) in Java creates a
BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with
a scale of 1), but it is actually equal to
0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that
matter, as a binary fraction of any finite length). Thus, the value
that is being passed in to the constructor is not exactly equal to
0.1, appearances notwithstanding.
So in your case, instead of using
double val = 77.48;
new BigDecimal(val);
use
BigDecimal.valueOf(val);
Value that is returned by BigDecimal.valueOf is equal to that resulting from invocation of Double.toString(double).
It prints 47.48000 if you use another MathContext:
BigDecimal b = new BigDecimal(d, MathContext.DECIMAL64);
Just pick the context you need.
You want to try String.format("%f", d), which will print your double in decimal notation. Don't use BigDecimal at all.
Regarding the precision issue: You are first storing 47.48 in the double c, then making a new BigDecimal from that double. The loss of precision is in assigning to c. You could do
BigDecimal b = new BigDecimal("47.48")
to avoid losing any precision.
Why not :
b = b.setScale(2, RoundingMode.HALF_UP);
It's printing out the actual, exact value of the double.
Double.toString(), which converts doubles to Strings, does not print the exact decimal value of the input -- if x is your double value, it prints out exactly enough digits that x is the closest double to the value it printed.
The point is that there is no such double as 47.48 exactly. Doubles store values as binary fractions, not as decimals, so it can't store exact decimal values. (That's what BigDecimal is for!)
The String.format syntax helps us convert doubles and BigDecimals to strings of whatever precision.
This java code:
double dennis = 0.00000008880000d;
System.out.println(dennis);
System.out.println(String.format("%.7f", dennis));
System.out.println(String.format("%.9f", new BigDecimal(dennis)));
System.out.println(String.format("%.19f", new BigDecimal(dennis)));
Prints:
8.88E-8
0.0000001
0.000000089
0.0000000888000000000
BigDecimal b = new BigDecimal(c).setScale(2,BigDecimal.ROUND_HALF_UP);
In Java 9 the following is deprecated:
BigDecimal.valueOf(d).setScale(2, BigDecimal.ROUND_HALF_UP);
instead use:
BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP);
Example:
double d = 47.48111;
System.out.println(BigDecimal.valueOf(d)); //Prints: 47.48111
BigDecimal bigDecimal = BigDecimal.valueOf(d).setScale(2, RoundingMode.HALF_UP);
System.out.println(bigDecimal); //Prints: 47.48

BigDecimal - comparing invariant to DecimalFormat

I generate one BIgDecimal using a format ###,###.## which results in 660,000.00 and then I try to comapre it against a new BigDecimal(660000.00) but it returns false every time. Is there a way to compare the values invariant to the initial format they were created in?
This is because the scale of the two BigDecimals differ:
System.out.println(new BigDecimal("660000.00").scale()); // 2
System.out.println(new BigDecimal(660000.00).scale()); // 0
To compare them regardless of scale, you could do:
BigDecimal bd1 = new BigDecimal("660000.00");
BigDecimal bd2 = new BigDecimal(660000.00);
int s = Math.max(bd1.scale(), bd2.scale());
.. bd1.setScale(s).equals(bd2.setScale(s)) .. // == true.
A side-note: Beware that a double literal, such as .1 for instance, does not represent the real value .1, but an approximation of it. (This is due to inaccuracies in the floating point representation.) This can be seen by doing
System.out.println(new BigDecimal(.1));
which prints 0.1000000000000000055511151231257827021181583404541015625
However, new BigDecimal(".1") does indeed represent exactly .1, which is why
new BigDecimal(.1).equals(new BigDecimal(".1"))
is false.

Comparing float and double primitives in Java

I came across a strange corner of Java.(It seems strange to me)
double dd = 3.5;
float ff = 3.5f;
System.out.println(dd==ff);
o/p: true
double dd = 3.2;
float ff = 3.2f;
System.out.println(dd==ff);
o/p: false
I observed that if we compare any two values (a float and a double as I mentioned in the example) with .5 OR .0 like 3.5, 234.5, 645.0
then output is true i.e. two values are equal otherwise output is false though they are equals.
Even I tried to make method strictfp but no luck.
Am I missing out on something.
Take a look at What every computer scientist should know about floating point numbers.
Squeezing infinitely many real numbers into a finite number of bits requires an approximate representation....
--- Edit to show what the above quote means ---
You shouldn't ever compare floats or doubles for equality; because, you can't really guarantee that the number you assign to the float or double is exact.
So
float x = 3.2f;
doesn't result in a float with a value of 3.2. It results in a float with a value of 3.2 plus or minus some very small error. Say 3.19999999997f. Now it should be obvious why the comparison won't work.
To compare floats for equality sanely, you need to check if the value is "close enough" to the same value, like so
float error = 0.000001 * second;
if ((first >= second - error) || (first <= second + error)) {
// close enough that we'll consider the two equal
...
}
The difference is that 3.5 can be represented exactly in both float and double - whereas 3.2 can't be represented exactly in either type... and the two closest approximations are different.
Imagine we had two fixed-precision decimal types, one of which stored 4 significant digits and one of which stored 8 significant digits, and we asked each of them to store the number closest to "a third" (however we might do that). Then one would have the value 0.3333 and one would have the value 0.33333333.
An equality comparison between float and double first converts the float to a double and then compares the two - which would be equivalent to converting 0.3333 in our "small decimal" type to 0.33330000. It would then compare 0.33330000 and 0.33333333 for equality, and give a result of false.
floating point is a binary format and it can represent numbers as a sum of powers of 2. e.g. 3.5 is 2 + 1 + 1/2.
float 3.2f as an approximation of 3.2 is
2 + 1 + 1/8+ 1/16+ 1/128+ 1/256+ 1/2048+ 1/4096+ 1/32768+ 1/65536+ 1/524288+ 1/1048576+ 1/4194304 + a small error
However double 3.2d as an approximation of 3.2 is
2 + 1 + 1/8+ 1/16+ 1/128+ 1/256+ 1/2048+ 1/4096+ 1/32768+ 1/65536+ 1/524288+ 1/1048576+ 1/8388608+ 1/16777216+ 1/134217728+ 1/268435456+ 1/2147483648+ 1/4294967296+ 1/34359738368+ 1/68719476736+ 1/549755813888+ 1/1099511627776+ 1/8796093022208+ 1/17592186044416+ 1/140737488355328+ 1/281474976710656+ 1/1125899906842624 + a smaller error
When you use floating point, you need to use appropriate rounding. If you use BigDecimal instead (and many people do) it has rounding built in.
double dd = 3.2;
float ff = 3.2f;
// compare the difference with the accuracy of float.
System.out.println(Math.abs(dd - ff) < 1e-7 * Math.abs(ff));
BTW the code I used to print the fractions for double.
double f = 3.2d;
double f2 = f - 3;
System.out.print("2+ 1");
for (long i = 2; i < 1L << 54; i <<= 1) {
f2 *= 2;
if (f2 >= 1) {
System.out.print("+ 1/" + i);
f2 -= 1;
}
}
System.out.println();
The common implementation of floating point numbers, IEEE754, allows for the precise representation of only those numbers which have a short, finite binary expansion, i.e. which are a sum of finitely many (nearby) powers of two. All other numbers cannot be precisely represented.
Since float and double have different sizes, the representation in both types for a non-representable value are different, and thus they compare as unequal.
(The length of the binary string is the size of the mantissa, so that's 24 for float, 53 for double and 64 for the 80-bit extended-precision float (not in Java). The scale is determined by the exponent.)
This should work:
BigDecimal ddBD = new BigDecimal(""+dd);
BigDecimal ffBD = new BigDecimal(""+ff);
// test for equality
ddBO.equals(ffBD);
Always work with BigDecimal when you want to compare floats or doubles
and always use the BigDecimal constructor with the String parameter!

Categories

Resources