Skip to content

Commit 37a11f9

Browse files
author
Thomas Kiley
authored
Merge pull request diffblue#1808 from LAJW/lajw/floating-point-to-java-string
TG-2667 Refactor and expose floating point to java conversion in expr2java.h
2 parents 1a88479 + b3f2320 commit 37a11f9

File tree

3 files changed

+173
-47
lines changed

3 files changed

+173
-47
lines changed

src/java_bytecode/expr2java.cpp

+5-47
Original file line numberDiff line numberDiff line change
@@ -228,53 +228,11 @@ std::string expr2javat::convert_constant(
228228
else if((src.type()==java_float_type()) ||
229229
(src.type()==java_double_type()))
230230
{
231-
ieee_floatt ieee_repr(to_constant_expr(src));
232-
std::string result;
233-
234-
bool is_java_float=(src.type()==java_float_type());
235-
if(ieee_repr.is_zero())
236-
{
237-
if(ieee_repr.get_sign())
238-
result+='-';
239-
result+="0.0";
240-
if(is_java_float)
241-
result+='f';
242-
return result;
243-
}
244-
245-
if(ieee_repr.is_NaN())
246-
{
247-
if(is_java_float)
248-
return "Float.NaN";
249-
else
250-
return "Double.NaN";
251-
}
252-
253-
if(ieee_repr.is_infinity())
254-
{
255-
if(is_java_float)
256-
{
257-
if(ieee_repr.get_sign())
258-
return "Float.NEGATIVE_INFINITY";
259-
else
260-
return "Float.POSITIVE_INFINITY";
261-
}
262-
else
263-
{
264-
if(ieee_repr.get_sign())
265-
return "Double.NEGATIVE_INFINITY";
266-
else
267-
return "Double.POSITIVE_INFINITY";
268-
}
269-
}
270-
271-
std::stringstream buffer;
272-
buffer << std::hexfloat;
273-
if(is_java_float)
274-
buffer << ieee_repr.to_float() << 'f';
275-
else
276-
buffer << ieee_repr.to_double();
277-
return buffer.str();
231+
// This converts NaNs to the canonical NaN
232+
const ieee_floatt ieee_repr(to_constant_expr(src));
233+
if(ieee_repr.is_double())
234+
return floating_point_to_java_string(ieee_repr.to_double());
235+
return floating_point_to_java_string(ieee_repr.to_float());
278236
}
279237

280238

src/java_bytecode/expr2java.h

+52
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Author: Daniel Kroening, [email protected]
1010
#ifndef CPROVER_JAVA_BYTECODE_EXPR2JAVA_H
1111
#define CPROVER_JAVA_BYTECODE_EXPR2JAVA_H
1212

13+
#include <cmath>
14+
#include <numeric>
15+
#include <sstream>
1316
#include <string>
1417
#include <ansi-c/expr2c_class.h>
1518

@@ -50,4 +53,53 @@ class expr2javat:public expr2ct
5053
std::string expr2java(const exprt &expr, const namespacet &ns);
5154
std::string type2java(const typet &type, const namespacet &ns);
5255

56+
/// Convert floating point number to a string without printing
57+
/// unnecessary zeros. Prints decimal if precision is not lost.
58+
/// Prints hexadecimal otherwise, and/or java-friendly NaN and Infinity
59+
template <typename float_type>
60+
std::string floating_point_to_java_string(float_type value)
61+
{
62+
const auto is_float = std::is_same<float_type, float>::value;
63+
static const std::string class_name = is_float ? "Float" : "Double";
64+
if(std::isnan(value))
65+
return class_name + ".NaN";
66+
if(std::isinf(value) && value >= 0.)
67+
return class_name + ".POSITIVE_INFINITY";
68+
if(std::isinf(value) && value <= 0.)
69+
return class_name + ".NEGATIVE_INFINITY";
70+
const std::string decimal = [&]() -> std::string { // NOLINT
71+
// Using ostringstream instead of to_string to get string without
72+
// trailing zeros
73+
std::ostringstream raw_stream;
74+
raw_stream << value;
75+
const auto raw_decimal = raw_stream.str();
76+
if(raw_decimal.find('.') == std::string::npos)
77+
return raw_decimal + ".0";
78+
return raw_decimal;
79+
}();
80+
const bool is_lossless = [&] { // NOLINT
81+
if(value == std::numeric_limits<float_type>::min())
82+
return true;
83+
try
84+
{
85+
return std::stod(decimal) == value;
86+
}
87+
catch(std::out_of_range)
88+
{
89+
return false;
90+
}
91+
}();
92+
const std::string lossless = [&]() -> std::string { // NOLINT
93+
if(is_lossless)
94+
return decimal;
95+
std::ostringstream stream;
96+
stream << std::hexfloat << value;
97+
return stream.str();
98+
}();
99+
const auto literal = is_float ? lossless + 'f' : lossless;
100+
if(is_lossless)
101+
return literal;
102+
return literal + " /* " + decimal + " */";
103+
}
104+
53105
#endif // CPROVER_JAVA_BYTECODE_EXPR2JAVA_H

unit/java_bytecode/expr2java.cpp

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*******************************************************************\
2+
3+
Module: Unit tests for expr-to-java string conversion
4+
5+
Author: DiffBlue Limited. All rights reserved.
6+
7+
\*******************************************************************/
8+
9+
#include <testing-utils/catch.hpp>
10+
#include <java_bytecode/expr2java.h>
11+
12+
TEST_CASE(
13+
"expr2java tests",
14+
"[core][java_bytecode][expr2java][floating_point_to_java_string]")
15+
{
16+
SECTION("0.0 double to string")
17+
{
18+
REQUIRE(floating_point_to_java_string(0.0) == "0.0");
19+
}
20+
21+
SECTION("0.0 float to string")
22+
{
23+
REQUIRE(floating_point_to_java_string(0.0f) == "0.0f");
24+
}
25+
26+
SECTION("-0.0 double to string")
27+
{
28+
REQUIRE(floating_point_to_java_string(-0.0) == "-0.0");
29+
}
30+
31+
SECTION("-0.0 float to string")
32+
{
33+
REQUIRE(floating_point_to_java_string(-0.0f) == "-0.0f");
34+
}
35+
36+
SECTION("1.0 double to string")
37+
{
38+
REQUIRE(floating_point_to_java_string(1.0) == "1.0");
39+
}
40+
41+
SECTION("1.0 float to string")
42+
{
43+
REQUIRE(floating_point_to_java_string(1.0f) == "1.0f");
44+
}
45+
46+
SECTION("-1.0 double to string")
47+
{
48+
REQUIRE(floating_point_to_java_string(-1.0) == "-1.0");
49+
}
50+
51+
SECTION("-1.0 float to string")
52+
{
53+
REQUIRE(floating_point_to_java_string(-1.0f) == "-1.0f");
54+
}
55+
56+
SECTION("Infinity double to string")
57+
{
58+
REQUIRE(
59+
floating_point_to_java_string(static_cast<double>(INFINITY)) ==
60+
"Double.POSITIVE_INFINITY");
61+
}
62+
63+
SECTION("Infinity float to string")
64+
{
65+
REQUIRE(
66+
floating_point_to_java_string(static_cast<float>(INFINITY)) ==
67+
"Float.POSITIVE_INFINITY");
68+
}
69+
70+
SECTION("Negative infinity double to string")
71+
{
72+
REQUIRE(
73+
floating_point_to_java_string(static_cast<double>(-INFINITY)) ==
74+
"Double.NEGATIVE_INFINITY");
75+
}
76+
77+
SECTION("Negative infinity float to string")
78+
{
79+
REQUIRE(
80+
floating_point_to_java_string(static_cast<float>(-INFINITY)) ==
81+
"Float.NEGATIVE_INFINITY");
82+
}
83+
84+
SECTION("Float NaN to string")
85+
{
86+
REQUIRE(
87+
floating_point_to_java_string(static_cast<float>(NAN)) == "Float.NaN");
88+
}
89+
90+
SECTION("Double NaN to string")
91+
{
92+
REQUIRE(
93+
floating_point_to_java_string(static_cast<double>(NAN)) == "Double.NaN");
94+
}
95+
96+
SECTION("Hex float to string (print a comment)")
97+
{
98+
const float value = std::strtod("0x1p+37f", nullptr);
99+
REQUIRE(
100+
floating_point_to_java_string(value) == "0x1p+37f /* 1.37439e+11 */");
101+
}
102+
103+
SECTION("Hex double to string (print a comment)")
104+
{
105+
const double value = std::strtod("0x1p+37f", nullptr);
106+
REQUIRE(
107+
floating_point_to_java_string(value) == "0x1p+37 /* 1.37439e+11 */");
108+
}
109+
110+
SECTION("Beyond numeric limits")
111+
{
112+
REQUIRE(
113+
floating_point_to_java_string(-5.56268e-309)
114+
.find("/* -5.56268e-309 */") != std::string::npos);
115+
}
116+
}

0 commit comments

Comments
 (0)