Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add relativelyCloseTo implementation #258

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 21 additions & 27 deletions hamcrest/src/main/java/org/hamcrest/number/IsCloseTo.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,54 @@
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

import static java.lang.Math.abs;


/**
* Is the value a number equal to a value within some range of
* acceptable error?
* Is the value a number equal to a value within some range of acceptable error?
*/
public class IsCloseTo extends TypeSafeMatcher<Double> {
private final double delta;
private final double value;
public class IsCloseTo extends TypeSafeMatcher<Number> {
private final double error;
private final double expected;

public IsCloseTo(double value, double error) {
this.delta = error;
this.value = value;
public IsCloseTo(double expected, double error) {
this.error = Math.abs(error);
this.expected = expected;
}

@Override
public boolean matchesSafely(Double item) {
return actualDelta(item) <= 0.0;
public boolean matchesSafely(Number actual) {
return calcError(actual) <= error;
}

@Override
public void describeMismatchSafely(Double item, Description mismatchDescription) {
public void describeMismatchSafely(Number item, Description mismatchDescription) {
mismatchDescription.appendValue(item)
.appendText(" differed by ")
.appendValue(actualDelta(item))
.appendText(" more than delta ")
.appendValue(delta);
.appendValue(calcError(item));
}

@Override
public void describeTo(Description description) {
description.appendText("a numeric value within ")
.appendValue(delta)
.appendValue(error)
.appendText(" of ")
.appendValue(value);
.appendValue(expected);
}

private double actualDelta(Double item) {
return abs(item - value) - delta;
private double calcError(Number actual) {
return Math.abs(expected - actual.doubleValue());
}

/**
* Creates a matcher of {@link Double}s that matches when an examined double is equal
* Creates a matcher of {@link Number}s that matches when an examined number is equal
* to the specified <code>operand</code>, within a range of +/- <code>error</code>.
* For example:
* <pre>assertThat(1.03, is(closeTo(1.0, 0.03)))</pre>
*
* @param operand
* the expected value of matching doubles
* @param expected
* the expected value of matching numbers
* @param error
* the delta (+/-) within which matches will be allowed
* the absolute error (+/-) within which matches will be allowed
*/
public static Matcher<Double> closeTo(double operand, double error) {
return new IsCloseTo(operand, error);
public static Matcher<Number> closeTo(double expected, double error) {
return new IsCloseTo(expected, error);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.hamcrest.number;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

/**
* Is the value a number equal to a value within some range of acceptable error?
*/
public class IsRelativelyCloseTo extends TypeSafeMatcher<Number> {
private final double relativeError;
private final double expected;

public IsRelativelyCloseTo(double expected, double relativeError) {
if (expected == 0) {
throw new IllegalArgumentException("relative error is undefined with an expected value of zero");
}

this.relativeError = Math.abs(relativeError);
this.expected = expected;
}

@Override
public boolean matchesSafely(Number actual) {
return calcRelativeError(actual) <= relativeError;
}

@Override
public void describeMismatchSafely(Number item, Description mismatchDescription) {
mismatchDescription.appendValue(item)
.appendText(" differed by ")
.appendValue(calcAbsoluteError(item));
}

@Override
public void describeTo(Description description) {
description.appendText("a numeric value within ")
.appendValue(expected * relativeError)
.appendText(" of ")
.appendValue(expected);
}

private double calcRelativeError(Number actual) {
return Math.abs(calcAbsoluteError(actual) / expected);
}

private double calcAbsoluteError(Number actual) {
return Math.abs(expected - actual.doubleValue());
}

/**
* Creates a matcher of {@link Number}s that matches when an examined number is equal
* to the specified <code>operand</code>, within a range of +/- <code>relative error</code>.
* The expected number must be measured on a
* <a href="https://en.wikipedia.org/wiki/Level_of_measurement#Ratio_scale">ratio scale</a>.
* For example:
* <pre>assertThat(103.0, is(closeTo(100.0, 0.03)))</pre>
*
* @param expected
* the expected value of matching numbers, which may not be zero
* @param relativeError
* the relative error (+/-) within which matches will be allowed
*/
public static Matcher<Number> relativelyCloseTo(double expected, double relativeError) {
return new IsRelativelyCloseTo(expected, relativeError);
}
}