Skip to content

Commit

Permalink
Support cast of double -> float of NaN. (#2240)
Browse files Browse the repository at this point in the history
* Support cast of double -> float of NaN.
Close #2239

* Actually test something in the test.
  • Loading branch information
abellgithub committed Oct 24, 2018
1 parent 8e1c249 commit b164f3a
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 15 deletions.
43 changes: 28 additions & 15 deletions pdal/PointView.hpp
Expand Up @@ -441,54 +441,67 @@ inline T PointView::getFieldAs(Dimension::Id dim,
{
assert(pointIndex < m_size);
T retval;
bool ok = false;
const Dimension::Detail *dd = layout()->dimDetail(dim);
double val;
Everything e;

switch (dd->type())
{
case Dimension::Type::Float:
val = getFieldInternal<float>(dim, pointIndex);
e.f = getFieldInternal<float>(dim, pointIndex);
ok = Utils::numericCast(e.f, retval);
break;
case Dimension::Type::Double:
val = getFieldInternal<double>(dim, pointIndex);
e.d = getFieldInternal<double>(dim, pointIndex);
ok = Utils::numericCast(e.d, retval);
break;
case Dimension::Type::Signed8:
val = getFieldInternal<int8_t>(dim, pointIndex);
e.s8 = getFieldInternal<int8_t>(dim, pointIndex);
ok = Utils::numericCast(e.s8, retval);
break;
case Dimension::Type::Signed16:
val = getFieldInternal<int16_t>(dim, pointIndex);
e.s16 = getFieldInternal<int16_t>(dim, pointIndex);
ok = Utils::numericCast(e.s16, retval);
break;
case Dimension::Type::Signed32:
val = getFieldInternal<int32_t>(dim, pointIndex);
e.s32 = getFieldInternal<int32_t>(dim, pointIndex);
ok = Utils::numericCast(e.s32, retval);
break;
case Dimension::Type::Signed64:
val = static_cast<double>(getFieldInternal<int64_t>(dim, pointIndex));
e.s64 = getFieldInternal<int64_t>(dim, pointIndex);
ok = Utils::numericCast(e.s64, retval);
break;
case Dimension::Type::Unsigned8:
val = getFieldInternal<uint8_t>(dim, pointIndex);
e.u8 = getFieldInternal<uint8_t>(dim, pointIndex);
ok = Utils::numericCast(e.u8, retval);
break;
case Dimension::Type::Unsigned16:
val = getFieldInternal<uint16_t>(dim, pointIndex);
e.u16 = getFieldInternal<uint16_t>(dim, pointIndex);
ok = Utils::numericCast(e.u16, retval);
break;
case Dimension::Type::Unsigned32:
val = getFieldInternal<uint32_t>(dim, pointIndex);
e.u32 = getFieldInternal<uint32_t>(dim, pointIndex);
ok = Utils::numericCast(e.u32, retval);
break;
case Dimension::Type::Unsigned64:
val = static_cast<double>(getFieldInternal<uint64_t>(dim, pointIndex));
e.u64 = getFieldInternal<uint64_t>(dim, pointIndex);
ok = Utils::numericCast(e.u64, retval);
break;
case Dimension::Type::None:
default:
val = 0;
ok = true;
retval = 0;
break;
}
} // switch

if (!Utils::numericCast(val, retval))
if (!ok)
{
std::ostringstream oss;
oss << "Unable to fetch data and convert as requested: ";
oss << Dimension::name(dim) << ":" <<
Dimension::interpretationName(dd->type()) <<
"(" << (double)val << ") -> " << Utils::typeidName<T>();
"(" << Utils::toDouble(e, dd->type()) << ") -> " <<
Utils::typeidName<T>();
throw pdal_error(oss.str());
}

Expand Down
22 changes: 22 additions & 0 deletions pdal/util/Utils.hpp
Expand Up @@ -728,6 +728,28 @@ namespace Utils
return false;
}

/**
Convert a numeric value from double to float. Specialization to handle
NaN.
\param in Value to convert.
\param out Converted value.
\return \c true if the conversion was successful, \c false if the
datatypes/input value don't allow conversion.
*/
template<>
inline bool numericCast(double in, float& out)
{
if ((in <= static_cast<double>((std::numeric_limits<float>::max)()) &&
in >= static_cast<double>(std::numeric_limits<float>::lowest())) ||
std::isnan(in))
{
out = static_cast<float>(in);
return true;
}
return false;
}

/**
Convert a value to its string representation by writing to a stringstream.
Expand Down
11 changes: 11 additions & 0 deletions test/unit/PointViewTest.cpp
Expand Up @@ -343,6 +343,17 @@ TEST(PointViewTest, issue1264)
EXPECT_THROW(v.setField(foo, 0, d), pdal_error);
}

TEST(PointViewTest, getFloatNan)
{
PointTable table;
PointLayoutPtr layout(table.layout());
layout->registerDim(Dimension::Id::ScanAngleRank, Dimension::Type::Float);
PointViewPtr view(new PointView(table));
const float scanAngleRank = std::numeric_limits<float>::quiet_NaN();
view->setField(Dimension::Id::ScanAngleRank, 0, scanAngleRank);
EXPECT_NO_THROW(view->getFieldAs<float>(Dimension::Id::ScanAngleRank, 0));
}

// Per discussions with @abellgithub (https://github.com/gadomski/PDAL/commit/c1d54e56e2de841d37f2a1b1c218ed723053f6a9#commitcomment-14415138)
// we only do bounds checking on `PointView`s when in debug mode.
#ifndef NDEBUG
Expand Down
24 changes: 24 additions & 0 deletions test/unit/UtilsTest.cpp
Expand Up @@ -431,3 +431,27 @@ TEST(UtilsTest, naninf)
d = -d;
EXPECT_EQ(Utils::toString(d), "-Infinity");
}


TEST(UtilsTest, numeric_cast)
{
bool ok;
double d;
float f;

f = std::numeric_limits<float>::quiet_NaN();
ok = Utils::numericCast(f, d);
EXPECT_TRUE(ok);
EXPECT_TRUE(std::isnan(d));

d = std::numeric_limits<double>::quiet_NaN();
ok = Utils::numericCast(d, f);
EXPECT_TRUE(ok);
EXPECT_TRUE(std::isnan(f));

d = (std::numeric_limits<float>::max)() * 2;
EXPECT_FALSE(Utils::numericCast(d, f));

d = (std::numeric_limits<float>::max)() / 2;
EXPECT_TRUE(Utils::numericCast(d, f));
}

0 comments on commit b164f3a

Please sign in to comment.