Discussion:
Why is static_cast<long long>(numeric_limits<double>::max()) < 0)?
(too old to reply)
legalize+ (Richard)
2014-06-18 05:48:44 UTC
Permalink
[Please do not mail me a copy of your followup]

I was trying to write something that would throw an assert when I did
a narrowing cast that truncated, so I wrote something like:

template <typename T, typename U>
T safe_narrowing_cast(const U value)
{
T const max_value = std::numeric_limits<T>::max();
U const max_u_value = static_cast<U>(max_value);
assert(value < max_u_value);
return static_cast<T>(value);
}

However, what surprised me is that when U was long long and T was
double, i.e. casting a long long to a double, that I got the
following:

max_value 1.7976931348623157e+308 const double
max_u_value -9223372036854775808 const __int64

I expected static_cast<double> to truncate, not act like
reinterpret_cast and just jam the bits in there.

My intention in the assert is to say "does the given value fit inside
the range of the target". In my specific case, value is something
like 70 or 200, so it clearly fits in the range of a double, but
obviously my assert isn't specified properly.

What am I missing?
--
"The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline>
The Computer Graphics Museum <http://computergraphicsmuseum.org>
The Terminals Wiki <http://terminals.classiccmp.org>
Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com>


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Jim Porter
2014-06-18 18:18:54 UTC
Permalink
Post by legalize+ (Richard)
I was trying to write something that would throw an assert when I did
[snip]
Post by legalize+ (Richard)
However, what surprised me is that when U was long long and T was
double, i.e. casting a long long to a double, that I got the
max_value 1.7976931348623157e+308 const double
max_u_value -9223372036854775808 const __int64
I expected static_cast<double> to truncate, not act like
reinterpret_cast and just jam the bits in there.
I assume you mean "static_cast<long long>" here, as in the subject of
the message. If not, you can probably ignore this message entirely. If
so, 4.9p2 of the standard says:

"A prvalue of a floating point type can be converted to a prvalue of an
integer type. The conversion truncates; that is, the fractional part is
discarded. The behavior is undefined if the truncated value cannot be
represented in the destination type."

Since std::numeric_limits<double>::max() doesn't fit in a long long, the
behavior is undefined, and so anything can happen.
Post by legalize+ (Richard)
My intention in the assert is to say "does the given value fit inside
the range of the target". In my specific case, value is something
like 70 or 200, so it clearly fits in the range of a double, but
obviously my assert isn't specified properly.
What am I missing?
The main problem is that the cast you've specified isn't a narrowing
cast at all! The range of a double is (much!) greater than that of long
long (assuming sizeof(long long) == 8). In an actual narrowing cast,
static_cast<U>(max_value) would be a *widening* cast, and so should be
safe. However, since you're actually calling safe_narrowing_cast to
*widen*, you've caused static_cast<U>(max_value) to be an unsafe
narrowing cast.

To solve this, you could static_assert that you're actually performing a
narrowing cast, i.e.:

std::numeric_limits<T>::max() < std::numeric_limits<U>::max()

You could also select an implementation based on whether the cast is
narrowing or widening by using enable_if.

- Jim
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Joshua Cranmer 🐧
2014-06-18 18:24:18 UTC
Permalink
Post by legalize+ (Richard)
What am I missing?
Undefined behavior. C and C++ love making lots of integer operations
undefined behavior, even if you assume 2's complement semantics. In the
case of floating point numbers, both floating point-to-integer and
integer-to-floating point conversions are undefined if they exceed the
range of permissible conversions, and what you're seeing is a
manifestation of that.

In the case of converting integers to floating points, the only possible
conversions that can exceed the floating point range are 64-bit (or
128-bit) integers being converted to half-precision floating point and
128-bit integers being converted to single-precision, but those both
involve non-standard types and are usually safely ignorable.
--
Beware of bugs in the above code; I have only proved it correct, not
tried it. -- Donald E. Knuth


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
news
2014-06-18 18:22:26 UTC
Permalink
Post by legalize+ (Richard)
[Please do not mail me a copy of your followup]
I was trying to write something that would throw an assert when I did
template <typename T, typename U>
T safe_narrowing_cast(const U value)
{
T const max_value = std::numeric_limits<T>::max();
U const max_u_value = static_cast<U>(max_value);
assert(value < max_u_value);
return static_cast<T>(value);
}
However, what surprised me is that when U was long long and T was
double, i.e. casting a long long to a double, that I got the
max_value 1.7976931348623157e+308 const double
max_u_value -9223372036854775808 const __int64
I expected static_cast<double> to truncate, not act like
reinterpret_cast and just jam the bits in there.
My intention in the assert is to say "does the given value fit inside
the range of the target". In my specific case, value is something
like 70 or 200, so it clearly fits in the range of a double, but
obviously my assert isn't specified properly.
What am I missing?
The conversion of a number to a signed integral type that lies outside
the allowable range invokes undefined behavior. typically it works by
taking the lower bits (similar to the required behavior with unsigned),
which allows the result to be negative.

Your function only really "works" for narrowing casts (where
std::numeric_limits<T>::max < std::numeric_limits<U>::max). Note that
the cases where the conversion of max_vaue may fail, are also the cases
where the conversion of value can't fail. Basically, the problem is that
your safe_narrowing_cast does an unsafe cast if you are doing a widening
cast.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Joshua Cranmer 🐧
2014-06-19 05:45:28 UTC
Permalink
Post by news
The conversion of a number to a signed integral type that lies outside
the allowable range invokes undefined behavior. typically it works by
taking the lower bits (similar to the required behavior with unsigned),
which allows the result to be negative.
Not quite true. Among integral conversions, converting to a signed
number is implementation-defined behavior, not undefined. It's signed
arithmetic operations that are undefined on nonrepresentable values.
--
Beware of bugs in the above code; I have only proved it correct, not
tried it. -- Donald E. Knuth


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Öö Tiib
2014-06-19 05:44:26 UTC
Permalink
Post by legalize+ (Richard)
I was trying to write something that would throw an assert when I did
template <typename T, typename U>
T safe_narrowing_cast(const U value)
{
T const max_value = std::numeric_limits<T>::max();
U const max_u_value = static_cast<U>(max_value);
assert(value < max_u_value);
return static_cast<T>(value);
}
However, what surprised me is that when U was long long and T was
double, i.e. casting a long long to a double, that I got the
max_value 1.7976931348623157e+308 const double
max_u_value -9223372036854775808 const __int64
Others already explained that it is outcome of undefined
behavior that you see here.
Post by legalize+ (Richard)
I expected static_cast<double> to truncate, not act like
reinterpret_cast and just jam the bits in there.
My intention in the assert is to say "does the given value fit inside
the range of the target".
Your template is too naive for that goal. Lets dump it and
try to get rid of all the ill that unsigned types cause to us
(if you ever use those) and also take minimums of value
ranges into account? That may do it (haven't tested):

template<typename T, typename F>
typename std::enable_if< std::is_signed<T>::value
&& std::is_signed<F>::value
, bool>::type canConvert(const F f)
{
return std::numeric_limits<T>::min() <= f
&& std::numeric_limits<T>::max() >= f;
}

template<typename T, typename F>
typename std::enable_if< std::is_unsigned<T>::value
&& std::is_signed<F>::value
, bool>::type canConvert(const F f)
{
return f >= 0
&& f <= std::numeric_limits<T>::max();
}

template<typename T, typename F>
typename std::enable_if< std::is_unsigned<F>::value
, bool>::type canConvert(const F f)
{
return f <= std::numeric_limits<T>::max();
}

So ... now we can likely 'assert( canConvert<T,U>(value));' in
that 'safe_cast'. For me it looks still too few to call it "safe".
The 'numeric_limits' 'min()' and 'max()' of underlying types
are almost never anywhere close to limits of actual meaningful
values within application. However I trust there are no UB
anymore.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Seungbeom Kim
2014-06-19 15:14:34 UTC
Permalink
Post by Öö Tiib
template<typename T, typename F>
typename std::enable_if< std::is_signed<T>::value
&& std::is_signed<F>::value
, bool>::type canConvert(const F f)
{
return std::numeric_limits<T>::min() <= f
&& std::numeric_limits<T>::max() >= f;
}
Note that std::numeric_limits<T>::min() is the *minimum positive
normalized value* if T is a floating type with denormalization,
which means std::numeric_limits<double>::min() is something like
2.22507e-308 and canConvert<double>(0) will return false.

I think you need std::numeric_limits<T>::lowest() here (though
I don't like the asymmetry of the names).
--
Seungbeom Kim


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Öö Tiib
2014-06-20 15:20:06 UTC
Permalink
Post by Seungbeom Kim
Post by Öö Tiib
template<typename T, typename F>
typename std::enable_if< std::is_signed<T>::value
&& std::is_signed<F>::value
, bool>::type canConvert(const F f)
{
return std::numeric_limits<T>::min() <= f
&& std::numeric_limits<T>::max() >= f;
}
Note that std::numeric_limits<T>::min() is the *minimum positive
normalized value* if T is a floating type with denormalization,
which means std::numeric_limits<double>::min() is something like
2.22507e-308 and canConvert<double>(0) will return false.
Yes, thanks, I keep mixing that 'min' up with 'lowest'.
Post by Seungbeom Kim
I think you need std::numeric_limits<T>::lowest() here (though
I don't like the asymmetry of the names).
That is the reason I sometimes mix them up. Asymmetry is perhaps
by tradition from C since there we have also 'DBL_MAX' and
'-DBL_MAX' as limits and 'DBL_MIN' as minimum positive
normalized value.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Seungbeom Kim
2014-06-21 19:16:29 UTC
Permalink
Post by Öö Tiib
Post by Seungbeom Kim
I think you need std::numeric_limits<T>::lowest() here (though
I don't like the asymmetry of the names).
That is the reason I sometimes mix them up. Asymmetry is perhaps
by tradition from C since there we have also 'DBL_MAX' and
'-DBL_MAX' as limits and 'DBL_MIN' as minimum positive
normalized value.
I guess you are probably right. The inconsistency in the meaning of
"_MIN" for integers and floating-points is not ideal but still okay in C
because you need to spell out the full name of the macro every time and
you should know what you mean by what you write, but carrying that
inconsistency over to C++ templates was a much worse and unfortunate
decision because you cannot know in advance what you mean by std::
numeric_limits<T>::min() without knowing the type T. As a result,
generic code should favor lowest(), and it is only for floating-points
that you really need min() instead of lowest().
--
Seungbeom Kim


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
legalize+ (Richard)
2014-06-25 07:17:26 UTC
Permalink
[Please do not mail me a copy of your followup]
Post by Öö Tiib
Your template is too naive for that goal.
I tried to crib the bare minimum from Robert Ramey's library here:
<https://github.com/robertramey/safe_numerics>

Apparently, I just didn't take enough of it.

Your comments would be better addressed to his library instead of my
poor man's immitation of it.
--
"The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline>
The Computer Graphics Museum <http://computergraphicsmuseum.org>
The Terminals Wiki <http://terminals.classiccmp.org>
Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com>


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Öö Tiib
2014-06-25 16:30:40 UTC
Permalink
Post by legalize+ (Richard)
Post by Öö Tiib
Your template is too naive for that goal.
<https://github.com/robertramey/safe_numerics>
Apparently, I just didn't take enough of it.
Your comments would be better addressed to his library instead of my
poor man's immitation of it.
Your OP was about need to do checked casts between floating point
types and integers.

The library at the link tells that it deals with integers. Templates
meant to work with integers often silently misbehave with floating
point types. There don't seem to be any tests with floating point
types.

I would still consider writing something like I wrote if you need
to check cast between integers and floating point types.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
legalize+ (Richard)
2014-06-27 07:04:33 UTC
Permalink
[Please do not mail me a copy of your followup]
Post by Öö Tiib
Post by legalize+ (Richard)
Post by Öö Tiib
Your template is too naive for that goal.
<https://github.com/robertramey/safe_numerics>
Apparently, I just didn't take enough of it.
Your comments would be better addressed to his library instead of my
poor man's immitation of it.
Your OP was about need to do checked casts between floating point
types and integers.
The library at the link tells that it deals with integers.
OK, so I was double-y stupid :-).

Thanks for pointing that out. I'll open a feature request with Robert
on his library through github.
--
"The Direct3D Graphics Pipeline" free book <http://tinyurl.com/d3d-pipeline>
The Computer Graphics Museum <http://computergraphicsmuseum.org>
The Terminals Wiki <http://terminals.classiccmp.org>
Legalize Adulthood! (my blog) <http://legalizeadulthood.wordpress.com>


[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
Continue reading on narkive:
Search results for 'Why is static_cast<long long>(numeric_limits<double>::max()) < 0)?' (Questions and Answers)
13
replies
Gamer terms?
started 2008-01-29 23:17:29 UTC
video & online games
Loading...