oh no not again - comparing two bowls of petunias - sorry - floats, for equality
Due to rounding errors, most floating-point numbers end up being
slightly imprecise.
https://www.floating-point-gui.de/errors/comparison/
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon;
}
}
What is the universal standard value for epsilon
, if, for instance, I wanted to hardcode it for an API that anyone could use for comparing any values?
java
add a comment |
Due to rounding errors, most floating-point numbers end up being
slightly imprecise.
https://www.floating-point-gui.de/errors/comparison/
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon;
}
}
What is the universal standard value for epsilon
, if, for instance, I wanted to hardcode it for an API that anyone could use for comparing any values?
java
3
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Sure, I could do that, but the difference is meant to berelative
thus universal?
– Adam
Nov 23 '18 at 15:34
I was hoping for an answer elaborating on the values ofFloat.MIN_NORMAL
andFloat.MIN_VALUE
.
– Adam
Nov 26 '18 at 17:42
add a comment |
Due to rounding errors, most floating-point numbers end up being
slightly imprecise.
https://www.floating-point-gui.de/errors/comparison/
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon;
}
}
What is the universal standard value for epsilon
, if, for instance, I wanted to hardcode it for an API that anyone could use for comparing any values?
java
Due to rounding errors, most floating-point numbers end up being
slightly imprecise.
https://www.floating-point-gui.de/errors/comparison/
public static boolean nearlyEqual(float a, float b, float epsilon) {
final float absA = Math.abs(a);
final float absB = Math.abs(b);
final float diff = Math.abs(a - b);
if (a == b) { // shortcut, handles infinities
return true;
} else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.MIN_NORMAL);
} else { // use relative error
return diff / Math.min((absA + absB), Float.MAX_VALUE) < epsilon;
}
}
What is the universal standard value for epsilon
, if, for instance, I wanted to hardcode it for an API that anyone could use for comparing any values?
java
java
asked Nov 23 '18 at 15:12
AdamAdam
2,28512252
2,28512252
3
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Sure, I could do that, but the difference is meant to berelative
thus universal?
– Adam
Nov 23 '18 at 15:34
I was hoping for an answer elaborating on the values ofFloat.MIN_NORMAL
andFloat.MIN_VALUE
.
– Adam
Nov 26 '18 at 17:42
add a comment |
3
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Sure, I could do that, but the difference is meant to berelative
thus universal?
– Adam
Nov 23 '18 at 15:34
I was hoping for an answer elaborating on the values ofFloat.MIN_NORMAL
andFloat.MIN_VALUE
.
– Adam
Nov 26 '18 at 17:42
3
3
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Sure, I could do that, but the difference is meant to be
relative
thus universal?– Adam
Nov 23 '18 at 15:34
Sure, I could do that, but the difference is meant to be
relative
thus universal?– Adam
Nov 23 '18 at 15:34
I was hoping for an answer elaborating on the values of
Float.MIN_NORMAL
and Float.MIN_VALUE
.– Adam
Nov 26 '18 at 17:42
I was hoping for an answer elaborating on the values of
Float.MIN_NORMAL
and Float.MIN_VALUE
.– Adam
Nov 26 '18 at 17:42
add a comment |
2 Answers
2
active
oldest
votes
There is no universal standard for epsilon because it strongly depends on what you are comparing and for what purpose. Every time you test for near equality of two floating-point numbers you need to consider two things:
- the absolute value of those numbers
- the kind of quantity they represent
For example PI is widely known to be "around 3.14", and that's good enough for most engineering applications but in Mathematics, or in spectral analysis (Astronomy) you need much higher precision.
Even withing the same domain the absolute values of numbers you compare will influence your choice of epsilon. Comparing circumference of Earth in kilometers to 40_000.00
vs distance to the Moon in meters to 384_400_000.00
have completely different tolerance of errors.
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
add a comment |
public static boolean nearlyEqual(double a, double b) {
if (a == b) {
return true;
} else if (a == 0 || b == 0) {
return false;
} else {
BigDecimal diff = new BigDecimal("" + a)
.subtract(new BigDecimal("" + b))
.abs();
int exponentA = new Double(Math.log10(Math.abs(a))).intValue();
int exponentB = new Double(Math.log10(Math.abs(b))).intValue();
int sigFigsA = getSignificantFigs(a);
int sigFigsB = getSignificantFigs(b);
BigDecimal epsilon = new BigDecimal("0.1")
.scaleByPowerOfTen(Math.min(exponentA, exponentB))
.scaleByPowerOfTen(-1 * Math.max(sigFigsA, sigFigsB));
return diff.compareTo(epsilon) < 0;
}
}
public static int getSignificantFigs(double inputDouble) {
// remove any exponent
BigDecimal input = new BigDecimal("" + Math.abs(inputDouble))
.stripTrailingZeros();
if (input.scale() < 0) {
int exponent = new Double(
Math.log10(Math.abs(inputDouble)))
.intValue();
input = input.scaleByPowerOfTen(-1 * exponent);
}
return input.precision();
}
And some tests:
@Test
public void testDoubleComparisonOnePositiveOneNegative() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.234E300D,
-1.234E300D
), "because one's positive and one's negative");
}
@Test
public void testDoubleComparisonZeroes() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
0D,
0.0D
), "zeroes");
}
@Test
public void testDoubleComparisonBasicDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
2.3450D,
2345.0D
), "typical numbers");
}
@Test
public void testDoubleComparisonMassivelyDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E-300D
), "big number vs tiny number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers2() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.2345678901234E300D,
1.2345678901233E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers3() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE
), "big number equals");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE * 0.1
), "big number");
}
@Test
public void testDoubleComparisonHugeNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
-1.2345678901234E300D,
-1.2345678901233E300D
), "big negative numbers");
}
@Test
public void testDoubleComparisonTinyPositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012345E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012344E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyTinyNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.5E-322D,
1.4E-322D
), "tiny tiny number");
}
@Test
public void testDoubleComparisonTinyTinyTinyNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL
), "tiny tiny number");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL * 0.1
), "tiny tiny numbers not same");
}
@Test
public void testDoubleComparisonThirds() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.3333333333333333333333333333333333333333D,
1.3333333333333333333333333333333333333333D
), "thirds");
}
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53449095%2foh-no-not-again-comparing-two-bowls-of-petunias-sorry-floats-for-equality%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
There is no universal standard for epsilon because it strongly depends on what you are comparing and for what purpose. Every time you test for near equality of two floating-point numbers you need to consider two things:
- the absolute value of those numbers
- the kind of quantity they represent
For example PI is widely known to be "around 3.14", and that's good enough for most engineering applications but in Mathematics, or in spectral analysis (Astronomy) you need much higher precision.
Even withing the same domain the absolute values of numbers you compare will influence your choice of epsilon. Comparing circumference of Earth in kilometers to 40_000.00
vs distance to the Moon in meters to 384_400_000.00
have completely different tolerance of errors.
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
add a comment |
There is no universal standard for epsilon because it strongly depends on what you are comparing and for what purpose. Every time you test for near equality of two floating-point numbers you need to consider two things:
- the absolute value of those numbers
- the kind of quantity they represent
For example PI is widely known to be "around 3.14", and that's good enough for most engineering applications but in Mathematics, or in spectral analysis (Astronomy) you need much higher precision.
Even withing the same domain the absolute values of numbers you compare will influence your choice of epsilon. Comparing circumference of Earth in kilometers to 40_000.00
vs distance to the Moon in meters to 384_400_000.00
have completely different tolerance of errors.
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
add a comment |
There is no universal standard for epsilon because it strongly depends on what you are comparing and for what purpose. Every time you test for near equality of two floating-point numbers you need to consider two things:
- the absolute value of those numbers
- the kind of quantity they represent
For example PI is widely known to be "around 3.14", and that's good enough for most engineering applications but in Mathematics, or in spectral analysis (Astronomy) you need much higher precision.
Even withing the same domain the absolute values of numbers you compare will influence your choice of epsilon. Comparing circumference of Earth in kilometers to 40_000.00
vs distance to the Moon in meters to 384_400_000.00
have completely different tolerance of errors.
There is no universal standard for epsilon because it strongly depends on what you are comparing and for what purpose. Every time you test for near equality of two floating-point numbers you need to consider two things:
- the absolute value of those numbers
- the kind of quantity they represent
For example PI is widely known to be "around 3.14", and that's good enough for most engineering applications but in Mathematics, or in spectral analysis (Astronomy) you need much higher precision.
Even withing the same domain the absolute values of numbers you compare will influence your choice of epsilon. Comparing circumference of Earth in kilometers to 40_000.00
vs distance to the Moon in meters to 384_400_000.00
have completely different tolerance of errors.
answered Nov 23 '18 at 15:22
Milo BemMilo Bem
822418
822418
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
add a comment |
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
You are completely ignoring the relative difference calculated. Regardless of the massive size or the minute size of the inputs, the relative difference is dependent on the number of significant figures. For astronomers or particle physicists.
– Adam
Nov 26 '18 at 17:42
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
If you already understand that the relative difference is dependent on the number of significant figures how can you expect a universal epsilon? This is what i tried to explain in my answer - there isn't one epsilon because it depends on the values you compare and the domain. Not all comparisons are between numbers with defined significant figures either, unless you want to limits your library's usage to specific scientific applications.
– Milo Bem
Nov 27 '18 at 9:29
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
I've come up with a method to calculate a suitable epsilon, as opposed to trying to find 'one size fits all'.
– Adam
Nov 27 '18 at 15:15
add a comment |
public static boolean nearlyEqual(double a, double b) {
if (a == b) {
return true;
} else if (a == 0 || b == 0) {
return false;
} else {
BigDecimal diff = new BigDecimal("" + a)
.subtract(new BigDecimal("" + b))
.abs();
int exponentA = new Double(Math.log10(Math.abs(a))).intValue();
int exponentB = new Double(Math.log10(Math.abs(b))).intValue();
int sigFigsA = getSignificantFigs(a);
int sigFigsB = getSignificantFigs(b);
BigDecimal epsilon = new BigDecimal("0.1")
.scaleByPowerOfTen(Math.min(exponentA, exponentB))
.scaleByPowerOfTen(-1 * Math.max(sigFigsA, sigFigsB));
return diff.compareTo(epsilon) < 0;
}
}
public static int getSignificantFigs(double inputDouble) {
// remove any exponent
BigDecimal input = new BigDecimal("" + Math.abs(inputDouble))
.stripTrailingZeros();
if (input.scale() < 0) {
int exponent = new Double(
Math.log10(Math.abs(inputDouble)))
.intValue();
input = input.scaleByPowerOfTen(-1 * exponent);
}
return input.precision();
}
And some tests:
@Test
public void testDoubleComparisonOnePositiveOneNegative() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.234E300D,
-1.234E300D
), "because one's positive and one's negative");
}
@Test
public void testDoubleComparisonZeroes() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
0D,
0.0D
), "zeroes");
}
@Test
public void testDoubleComparisonBasicDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
2.3450D,
2345.0D
), "typical numbers");
}
@Test
public void testDoubleComparisonMassivelyDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E-300D
), "big number vs tiny number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers2() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.2345678901234E300D,
1.2345678901233E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers3() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE
), "big number equals");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE * 0.1
), "big number");
}
@Test
public void testDoubleComparisonHugeNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
-1.2345678901234E300D,
-1.2345678901233E300D
), "big negative numbers");
}
@Test
public void testDoubleComparisonTinyPositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012345E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012344E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyTinyNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.5E-322D,
1.4E-322D
), "tiny tiny number");
}
@Test
public void testDoubleComparisonTinyTinyTinyNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL
), "tiny tiny number");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL * 0.1
), "tiny tiny numbers not same");
}
@Test
public void testDoubleComparisonThirds() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.3333333333333333333333333333333333333333D,
1.3333333333333333333333333333333333333333D
), "thirds");
}
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
add a comment |
public static boolean nearlyEqual(double a, double b) {
if (a == b) {
return true;
} else if (a == 0 || b == 0) {
return false;
} else {
BigDecimal diff = new BigDecimal("" + a)
.subtract(new BigDecimal("" + b))
.abs();
int exponentA = new Double(Math.log10(Math.abs(a))).intValue();
int exponentB = new Double(Math.log10(Math.abs(b))).intValue();
int sigFigsA = getSignificantFigs(a);
int sigFigsB = getSignificantFigs(b);
BigDecimal epsilon = new BigDecimal("0.1")
.scaleByPowerOfTen(Math.min(exponentA, exponentB))
.scaleByPowerOfTen(-1 * Math.max(sigFigsA, sigFigsB));
return diff.compareTo(epsilon) < 0;
}
}
public static int getSignificantFigs(double inputDouble) {
// remove any exponent
BigDecimal input = new BigDecimal("" + Math.abs(inputDouble))
.stripTrailingZeros();
if (input.scale() < 0) {
int exponent = new Double(
Math.log10(Math.abs(inputDouble)))
.intValue();
input = input.scaleByPowerOfTen(-1 * exponent);
}
return input.precision();
}
And some tests:
@Test
public void testDoubleComparisonOnePositiveOneNegative() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.234E300D,
-1.234E300D
), "because one's positive and one's negative");
}
@Test
public void testDoubleComparisonZeroes() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
0D,
0.0D
), "zeroes");
}
@Test
public void testDoubleComparisonBasicDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
2.3450D,
2345.0D
), "typical numbers");
}
@Test
public void testDoubleComparisonMassivelyDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E-300D
), "big number vs tiny number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers2() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.2345678901234E300D,
1.2345678901233E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers3() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE
), "big number equals");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE * 0.1
), "big number");
}
@Test
public void testDoubleComparisonHugeNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
-1.2345678901234E300D,
-1.2345678901233E300D
), "big negative numbers");
}
@Test
public void testDoubleComparisonTinyPositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012345E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012344E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyTinyNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.5E-322D,
1.4E-322D
), "tiny tiny number");
}
@Test
public void testDoubleComparisonTinyTinyTinyNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL
), "tiny tiny number");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL * 0.1
), "tiny tiny numbers not same");
}
@Test
public void testDoubleComparisonThirds() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.3333333333333333333333333333333333333333D,
1.3333333333333333333333333333333333333333D
), "thirds");
}
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
add a comment |
public static boolean nearlyEqual(double a, double b) {
if (a == b) {
return true;
} else if (a == 0 || b == 0) {
return false;
} else {
BigDecimal diff = new BigDecimal("" + a)
.subtract(new BigDecimal("" + b))
.abs();
int exponentA = new Double(Math.log10(Math.abs(a))).intValue();
int exponentB = new Double(Math.log10(Math.abs(b))).intValue();
int sigFigsA = getSignificantFigs(a);
int sigFigsB = getSignificantFigs(b);
BigDecimal epsilon = new BigDecimal("0.1")
.scaleByPowerOfTen(Math.min(exponentA, exponentB))
.scaleByPowerOfTen(-1 * Math.max(sigFigsA, sigFigsB));
return diff.compareTo(epsilon) < 0;
}
}
public static int getSignificantFigs(double inputDouble) {
// remove any exponent
BigDecimal input = new BigDecimal("" + Math.abs(inputDouble))
.stripTrailingZeros();
if (input.scale() < 0) {
int exponent = new Double(
Math.log10(Math.abs(inputDouble)))
.intValue();
input = input.scaleByPowerOfTen(-1 * exponent);
}
return input.precision();
}
And some tests:
@Test
public void testDoubleComparisonOnePositiveOneNegative() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.234E300D,
-1.234E300D
), "because one's positive and one's negative");
}
@Test
public void testDoubleComparisonZeroes() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
0D,
0.0D
), "zeroes");
}
@Test
public void testDoubleComparisonBasicDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
2.3450D,
2345.0D
), "typical numbers");
}
@Test
public void testDoubleComparisonMassivelyDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E-300D
), "big number vs tiny number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers2() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.2345678901234E300D,
1.2345678901233E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers3() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE
), "big number equals");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE * 0.1
), "big number");
}
@Test
public void testDoubleComparisonHugeNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
-1.2345678901234E300D,
-1.2345678901233E300D
), "big negative numbers");
}
@Test
public void testDoubleComparisonTinyPositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012345E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012344E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyTinyNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.5E-322D,
1.4E-322D
), "tiny tiny number");
}
@Test
public void testDoubleComparisonTinyTinyTinyNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL
), "tiny tiny number");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL * 0.1
), "tiny tiny numbers not same");
}
@Test
public void testDoubleComparisonThirds() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.3333333333333333333333333333333333333333D,
1.3333333333333333333333333333333333333333D
), "thirds");
}
public static boolean nearlyEqual(double a, double b) {
if (a == b) {
return true;
} else if (a == 0 || b == 0) {
return false;
} else {
BigDecimal diff = new BigDecimal("" + a)
.subtract(new BigDecimal("" + b))
.abs();
int exponentA = new Double(Math.log10(Math.abs(a))).intValue();
int exponentB = new Double(Math.log10(Math.abs(b))).intValue();
int sigFigsA = getSignificantFigs(a);
int sigFigsB = getSignificantFigs(b);
BigDecimal epsilon = new BigDecimal("0.1")
.scaleByPowerOfTen(Math.min(exponentA, exponentB))
.scaleByPowerOfTen(-1 * Math.max(sigFigsA, sigFigsB));
return diff.compareTo(epsilon) < 0;
}
}
public static int getSignificantFigs(double inputDouble) {
// remove any exponent
BigDecimal input = new BigDecimal("" + Math.abs(inputDouble))
.stripTrailingZeros();
if (input.scale() < 0) {
int exponent = new Double(
Math.log10(Math.abs(inputDouble)))
.intValue();
input = input.scaleByPowerOfTen(-1 * exponent);
}
return input.precision();
}
And some tests:
@Test
public void testDoubleComparisonOnePositiveOneNegative() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.234E300D,
-1.234E300D
), "because one's positive and one's negative");
}
@Test
public void testDoubleComparisonZeroes() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
0D,
0.0D
), "zeroes");
}
@Test
public void testDoubleComparisonBasicDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
2.3450D,
2345.0D
), "typical numbers");
}
@Test
public void testDoubleComparisonMassivelyDifferentNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E-300D
), "big number vs tiny number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789E300D,
1.23456789E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers2() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.2345678901234E300D,
1.2345678901233E300D
), "big number");
}
@Test
public void testDoubleComparisonHugePositiveNumbers3() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE
), "big number equals");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MAX_VALUE,
Double.MAX_VALUE * 0.1
), "big number");
}
@Test
public void testDoubleComparisonHugeNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
-1.2345678901234E300D,
-1.2345678901233E300D
), "big negative numbers");
}
@Test
public void testDoubleComparisonTinyPositiveNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012345E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyNegativeNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.23456789012345E-300D,
1.23456789012344E-300D
), "tiny number");
}
@Test
public void testDoubleComparisonTinyTinyNumbers() {
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
1.5E-322D,
1.4E-322D
), "tiny tiny number");
}
@Test
public void testDoubleComparisonTinyTinyTinyNumbers() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL
), "tiny tiny number");
Assertions.assertFalse(
GeneralUtils.nearlyEqual(
Double.MIN_NORMAL,
Double.MIN_NORMAL * 0.1
), "tiny tiny numbers not same");
}
@Test
public void testDoubleComparisonThirds() {
Assertions.assertTrue(
GeneralUtils.nearlyEqual(
1.3333333333333333333333333333333333333333D,
1.3333333333333333333333333333333333333333D
), "thirds");
}
answered Nov 27 '18 at 15:18
AdamAdam
2,28512252
2,28512252
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
add a comment |
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
I'm confused on what are you trying to achieve here. You basically reimplemented equals. Is there any case when different numbers return true, as in "nearly equal"?
– Milo Bem
Nov 27 '18 at 17:31
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53449095%2foh-no-not-again-comparing-two-bowls-of-petunias-sorry-floats-for-equality%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
3
Why not letting the users of your API decide what Epsilon they need?
– maio290
Nov 23 '18 at 15:14
Sure, I could do that, but the difference is meant to be
relative
thus universal?– Adam
Nov 23 '18 at 15:34
I was hoping for an answer elaborating on the values of
Float.MIN_NORMAL
andFloat.MIN_VALUE
.– Adam
Nov 26 '18 at 17:42