diff --git a/src/main/java/edu/uiowa/cs/clc/kind2/results/RealValue.java b/src/main/java/edu/uiowa/cs/clc/kind2/results/RealValue.java index 51de0b0..fa04062 100644 --- a/src/main/java/edu/uiowa/cs/clc/kind2/results/RealValue.java +++ b/src/main/java/edu/uiowa/cs/clc/kind2/results/RealValue.java @@ -9,17 +9,48 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import edu.uiowa.cs.clc.kind2.Kind2Exception; import java.math.BigDecimal; import java.math.BigInteger; /** * The value of kind2 rational number. + * + *
Not every one of them is a number. Kind 2 reports an infinity and an
+ * undefined value as a fraction over a zero denominator, which is how a
+ * division by zero in the model reaches the counterexample: {@code 1/0} and
+ * {@code -1/0} for the infinities, {@code 0/0} for a value it could not
+ * determine. {@link #getKind} says which of the four a value is, and
+ * {@link #getValue} is the number only for a finite one.
*/
public class RealValue extends Value
{
/**
- * the value of the rational number.
+ * What a rational reported by Kind 2 turned out to be.
+ */
+ public enum Kind
+ {
+ /**
+ * A number, which {@link RealValue#getValue} returns.
+ */
+ FINITE,
+ /**
+ * Positive infinity, which Kind 2 writes as {@code 1/0}.
+ */
+ POSITIVE_INFINITY,
+ /**
+ * Negative infinity, which Kind 2 writes as {@code -1/0}.
+ */
+ NEGATIVE_INFINITY,
+ /**
+ * A value Kind 2 could not determine, which it writes as {@code 0/0}.
+ */
+ UNDEFINED
+ }
+
+ /**
+ * the value of the rational number, or null when it is not a number.
*/
private final BigDecimal value;
/**
@@ -30,6 +61,10 @@ public class RealValue extends Value
* The denominator of the rational number
*/
private final BigInteger denominator;
+ /**
+ * Which of the four kinds of rational this is.
+ */
+ private final Kind kind;
/**
* Constructs a real value from one Kind 2 stream value.
@@ -46,30 +81,131 @@ public RealValue(StepValue kind2StepValue, Type kind2Type, JsonElement jsonEleme
JsonObject jsonObject = jsonElement.getAsJsonObject();
numerator = new BigInteger(jsonObject.get(Labels.numerator).getAsString());
denominator = new BigInteger(jsonObject.get(Labels.denominator).getAsString());
- value = new BigDecimal(numerator).divide(new BigDecimal(denominator),
- Result.getRealPrecision(), Result.getRealRoundingMode());
+ kind = kindOf(numerator, denominator);
+ // Dividing is only possible, and only means anything, for a finite one
+ value = kind == Kind.FINITE
+ ? new BigDecimal(numerator).divide(new BigDecimal(denominator),
+ Result.getRealPrecision(), Result.getRealRoundingMode())
+ : null;
}
else
{
value = jsonElement.getAsBigDecimal();
numerator = value.unscaledValue();
denominator = BigInteger.TEN.pow(value.scale());
+ kind = Kind.FINITE;
}
}
+ /**
+ * The kind a fraction denotes. A zero denominator carries an infinity whose
+ * sign is the sign of the numerator, or, with a zero numerator, a value
+ * Kind 2 could not determine.
+ */
+ private static Kind kindOf(BigInteger numerator, BigInteger denominator)
+ {
+ if (denominator.signum() != 0)
+ {
+ return Kind.FINITE;
+ }
+ if (numerator.signum() > 0)
+ {
+ return Kind.POSITIVE_INFINITY;
+ }
+ if (numerator.signum() < 0)
+ {
+ return Kind.NEGATIVE_INFINITY;
+ }
+ return Kind.UNDEFINED;
+ }
+
+ /**
+ * Returns which of the four kinds of rational this value is.
+ *
+ * @return the kind of this value
+ */
+ public Kind getKind()
+ {
+ return kind;
+ }
+
+ /**
+ * Returns whether this value is a number, and so whether
+ * {@link #getValue} returns one.
+ *
+ * @return {@code true} if this value is finite
+ */
+ public boolean isFinite()
+ {
+ return kind == Kind.FINITE;
+ }
+
/**
* Returns the value of the rational number.
*
* @return the value of the rational number.
+ * @throws Kind2Exception if this value is not a number, which
+ * {@link #isFinite} tells apart beforehand
*/
public BigDecimal getValue()
{
+ if (value == null)
+ {
+ throw new Kind2Exception(
+ "This value is " + kind + " rather than a number, so it has no "
+ + "decimal value. Kind 2 reports it as " + this
+ + ". Use isFinite() to tell the two apart, or getDoubleValue().");
+ }
return value;
}
+ /**
+ * Returns this value as a {@code double}, which, unlike a
+ * {@link BigDecimal}, can carry an infinity and a value Kind 2 could not
+ * determine. A finite one is rounded to the nearest {@code double}.
+ *
+ * @return this value as a {@code double}
+ */
+ public double getDoubleValue()
+ {
+ switch (kind)
+ {
+ case POSITIVE_INFINITY:
+ return Double.POSITIVE_INFINITY;
+ case NEGATIVE_INFINITY:
+ return Double.NEGATIVE_INFINITY;
+ case UNDEFINED:
+ return Double.NaN;
+ default:
+ return value.doubleValue();
+ }
+ }
+
+ /**
+ * Returns the numerator of this value expressed as a fraction.
+ *
+ * @return the numerator of this value
+ */
+ public BigInteger getNumerator()
+ {
+ return numerator;
+ }
+
+ /**
+ * Returns the denominator of this value expressed as a fraction, which is
+ * zero for every value that is not a number.
+ *
+ * @return the denominator of this value
+ */
+ public BigInteger getDenominator()
+ {
+ return denominator;
+ }
+
@Override
public String toString()
{
- return value.toString();
+ // What Kind 2 itself prints for these, in its plain text and its XML
+ return value == null ? numerator + "/" + denominator : value.toString();
}
}
diff --git a/src/test/java/edu/uiowa/cs/clc/kind2/results/Kind2ResultTests.java b/src/test/java/edu/uiowa/cs/clc/kind2/results/Kind2ResultTests.java
index 5fc2569..277890c 100644
--- a/src/test/java/edu/uiowa/cs/clc/kind2/results/Kind2ResultTests.java
+++ b/src/test/java/edu/uiowa/cs/clc/kind2/results/Kind2ResultTests.java
@@ -9,6 +9,7 @@
package edu.uiowa.cs.clc.kind2.results;
import java.io.IOException;
+import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.nio.file.Files;
@@ -16,7 +17,11 @@
import java.util.Arrays;
import java.util.List;
+import edu.uiowa.cs.clc.kind2.Kind2Exception;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -664,4 +669,53 @@ void machineIntegerCounterExample()
IntValue wide = (IntValue) streams.get(2).getStepValues().get(0).getKind2Value();
assertEquals(new BigInteger("16777215"), wide.getValue());
}
+ /**
+ * A counterexample holding the rationals that are not numbers. A division
+ * by zero in the model reaches it as a fraction over a zero denominator,
+ * and reading one used to throw out of the constructor of {@link
+ * RealValue} and take the whole result with it.
+ */
+ @Test
+ void realValuesThatAreNotNumbers()
+ {
+ String json = "[{'objectType' : 'log','level' : 'info','source' : 'parse','value' : 'kind2 v3.0.0'},{'objectType' : 'analysisStart','top' : 'bla','concrete' : [],'abstract' : [],'assumptions' : []},{'objectType' : 'property','name' : 'InvProp[l18c3]','source' : 'PropAnnot','file' : './examples/div.lus','line' : 18,'column' : 3,'runtime' : {'unit' : 'sec', 'timeout' : false, 'value' : 0.010},'k' : 0,'answer' : {'source' : 'bmc', 'value' : 'falsifiable'},'counterExample' :[{'blockType' : 'node','name' : 'bla','streams' :[{'name' : 'i','type' : 'real','class' : 'input','instantValues' : [[0, {'num': 0, 'den': 1}]]},{'name' : 'inf_pos','type' : 'real','class' : 'local','instantValues' : [[0, {'num': 1, 'den': 0}]]},{'name' : 'inf_neg','type' : 'real','class' : 'local','instantValues' : [[0, {'num': -1, 'den': 0}]]},{'name' : 'inf_p_div_n','type' : 'real','class' : 'local','instantValues' : [[0, {'num': 0, 'den': 0}]]}]}]},{'objectType' : 'analysisStop'}]";
+
+ Result result = Result.analyzeJsonResult(json);
+ assertNotNull(result.getRoot());
+
+ Property property = result.getRoot().getAnalyses().get(0).getFalsifiedProperties().get(0);
+ List