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 method to check if NumberNode is set to zero #1385

Merged
merged 1 commit into from
Sep 4, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package software.amazon.smithy.model.node;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
Expand All @@ -31,7 +32,7 @@
public final class NumberNode extends Node {

private final Number value;
private String stringCache;
private final String stringCache;

public NumberNode(Number value, SourceLocation sourceLocation) {
super(sourceLocation);
Expand Down Expand Up @@ -94,6 +95,38 @@ public Optional<NumberNode> asNumberNode() {
return Optional.of(this);
}

/**
* Returns true if the value of the number contained in the number node is zero,
* accounting for float, double, bigInteger, bigDecimal, and other numeric types
* (e.g., 0, 0.0, etc).
*
* <p>Note that -0 and +0 are considered 0. However, NaN is not considered zero.
* When unknown number types are encountered, this method will return true if the
* toString of the given number returns "0", or "0.0". Other kinds of unknown
* number types will be treated like a double.
*
* <p>Double and float comparisons to zero are exact and use no rounding. The majority
* of values seen by this method come from models that use "0" or "0.0". However,
* we can improve this in the future with some kind of epsilon if the need arises.
*
* @return Returns true if set to zero.
*/
public boolean isZero() {
// Do a cheap test based on the serialized value of the number first.
// This test covers byte, short, integer, and long.
if (toString().equals("0") || toString().equals("0.0")) {
return true;
} else if (value instanceof BigDecimal) {
return value.equals(BigDecimal.ZERO);
} else if (value instanceof BigInteger) {
return value.equals(BigInteger.ZERO);
} else if (value instanceof Float) {
return value.floatValue() == 0f;
} else {
return value.doubleValue() == 0d;
}
}

@Override
public boolean equals(Object other) {
return other instanceof NumberNode && stringCache.equals(((NumberNode) other).stringCache);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import software.amazon.smithy.model.SourceLocation;

Expand Down Expand Up @@ -153,4 +157,88 @@ public void equalityAndHashCodeTest() {
public void convertsToNumberNode() {
assertTrue(Node.from(10).asNumberNode().isPresent());
}

@Test
public void testsForZero() {
Map<Number, Boolean> cases = new HashMap<>();
cases.put((byte) 0, true);
cases.put((short) 0, true);
cases.put(0, true);
cases.put(0L, true);
cases.put(0.0f, true);
cases.put(-0.0f, true);
cases.put(0.0d, true);
cases.put(-0.0d, true);
cases.put(new BigInteger("0"), true);
cases.put(new BigInteger("+0"), true);
cases.put(new BigInteger("-0"), true);
cases.put(new BigDecimal("0"), true);
cases.put(new BigDecimal("+0"), true);
cases.put(new BigDecimal("-0"), true);
cases.put(BigInteger.ZERO, true);
cases.put(BigDecimal.ZERO, true);
cases.put(new Number() {
@Override
public int intValue() {
return 0;
}

@Override
public long longValue() {
return 0;
}

@Override
public float floatValue() {
return 0;
}

@Override
public double doubleValue() {
return 0;
}
}, true);
cases.put(new Number() {
@Override
public int intValue() {
return 0;
}

@Override
public long longValue() {
return 0;
}

@Override
public float floatValue() {
return 0;
}

@Override
public double doubleValue() {
return 0;
}

@Override
public String toString() {
return "0.0";
}
}, true);

cases.put((byte) 1, false);
cases.put((short) 1, false);
cases.put(1, false);
cases.put(1L, false);
cases.put(0.01f, false);
cases.put(Float.NaN, false);
cases.put(0.01d, false);
cases.put(Double.NaN, false);
cases.put(new BigInteger("1"), false);
cases.put(new BigDecimal("0.01"), false);

cases.forEach((k, v) -> {
boolean result = new NumberNode(k, SourceLocation.NONE).isZero();
assertEquals(v, result, "Expected " + k + " to be " + v);
});
}
}