Skip to content
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
5 changes: 3 additions & 2 deletions src/main/java/java/lang/String.java
Original file line number Diff line number Diff line change
Expand Up @@ -4266,8 +4266,9 @@ public static String valueOf(float f) {
* actual program.
*/
public static String valueOf(double d) {
// DIFFBLUE MODEL LIBRARY This is treated internally in CBMC
return CProver.nondetWithoutNullForNotModelled();
// DIFFBLUE MODEL LIBRARY we cast the number down to float because
// the string solver only knows how to convert floats to string
return valueOf(CProver.doubleToFloat(d));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ How is the result of this method call different from just casting d to type float?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In doubleToFloat we're ensuring that there's no loss of precision in the conversion, so that we convert the correct number to string. Say we had double x, then converted it to double y, and then we converted that to String z. z is a string representation of y, but not necessarily of x. After a discussion with @romainbrenguier I went for the doubleToFloat implementation in order to make it more likely that the string representation of x equals the string representation of y.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the precision problem but I don't understand how doubleToFloat avoids this problem. As float has fewer bits than double, wouldn't there always be a loss of precision? And can we be sure that for every double d, there is actually a float f such that d == (double) f;? Since there are strictly fewer floats than doubles this doesn't look right... and would mean that we sometimes can't get past the assume in doubleToFloat.
Maybe we could briefly discuss this in person? It's possible that I'm just missing something.
In any case, some tests in another repository would be great to see what this actually does/fixes.

// return Double.toString(d);
}

Expand Down
7 changes: 3 additions & 4 deletions src/main/java/java/lang/StringBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,10 @@ public synchronized StringBuffer append(char c) {
*/
@Override
public synchronized StringBuffer append(int i) {
// DIFFBLUE MODEL LIBRARY this is replaced internally
// toStringCache = null;
// super.append(i);
// return this;
return CProver.nondetWithoutNullForNotModelled();
return append(String.valueOf(i));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ This isn't related to floats so should probably be in a separate commit.
❔ Why is this done for int but not long, char etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. It's a follow-up from diffblue/cbmc#4633. I've split this into a separate commit.

Other types should be coming later too, but at the moment I concentrated on the bugs that I've seen in our benchmarks, and am handling them one by one.

}

/**
Expand Down Expand Up @@ -576,7 +575,7 @@ public synchronized StringBuffer append(float f) {
// toStringCache = null;
// super.append(f);
// return this;
return CProver.nondetWithoutNullForNotModelled();
return append(String.valueOf(f));
}

/**
Expand All @@ -589,7 +588,7 @@ public synchronized StringBuffer append(double d) {
// toStringCache = null;
// super.append(d);
// return this;
return CProver.nondetWithoutNullForNotModelled();
return append(String.valueOf(d));
}

/**
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/cprover/CProver.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,27 @@ public static String classIdentifier(Object object)
{
return object.getClass().getCanonicalName();
}

/**
* This method converts a double to a float and adds assumes that the conversion
* did not result any loss of precision. Calling this method is useful when we
* need to call a certain complicated operation on double, but we only have it
* implemented for floats.
*
* The method is a workaround for the current limitations of the string solver,
* which is able to convert float to String but not double to String. Once this
* limitation goes away, the method and its usages can be removed.
*
* The method itself has limitations:
* * There are doubles that can't be converted to float and back without loss
* of precision, so the assertion will sometimes be violated
* * Even if the assertion is satisfied for numbers d and converted, these
* might not have the same String representation.
*/
public static float doubleToFloat(double d)
{
float converted = nondetFloat();
assert(d == (double) converted);
return converted;
}
}