How to prevent such code from compiling?
#include <vector>
#include <limits>
#include <iostream>
#include <cstdint>
int main() {
std::vector<int16_t> v;
v.emplace_back(std::numeric_limits<uint64_t>::max());
std::cout << v.back() << std::endl;
return 0;
}
g++ and clang with -std=c++14 -Wall -Wextra -Werror -pedantic -Wold-style-cast -Wconversion -Wsign-conversion
don't even warn about it. The example also compiles without warnings with std::vector<uint16_t>
We get this loss of precision all the time in our computing, because our numbers are being converted from decimal into binary floating point. Many things that look fine in decimal, such as 0.1 or 0.4, are repeating decimals in binary. In binary, 0.5 has a lovely representation: 0.1.
Fractional bits are reserved according to the quantization step that you choose. Another workaround is to float (4 bytes) to int (4 bytes). If the converted integer is in range of short values, then all bits can be retrieved. Float to Int to Short : short n = (short) (Float.floatToRawIntBits (x)); Hope that this will be of help to you.
To resolve the behavior, most programmers either ensure that the value is greater or less than what is needed, or they get and use a Binary Coded Decimal (BCD) library that will maintain the precision. Binary representation of floating-point values affects the precision and accuracy of floating-point calculations.
Add -Wsystem-headers
to the command line. Among the many spurious warnings you will find the desired warning.
In file included from (...)include/c++/6.3.0/x86_64-w64-mingw32/bits/c++allocator.h:33:0,
from (...)include/c++/6.3.0/bits/allocator.h:46,
from (...)include/c++/6.3.0/vector:61,
from test.cpp:1:
(...)include/c++/6.3.0/ext/new_allocator.h: In instantiation of 'void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = short int; _Args = {long long unsigned int}; _Tp = short int]':
(...)include/c++/6.3.0/bits/alloc_traits.h:455:4: required from 'static void std::allocator_traits<std::allocator<_Tp1> >::construct(std::allocator_traits<std::allocator<_Tp1> >::allocator_type&, _Up*, _Args&& ...) [with _Up = short int; _Args = {long long unsigned int}; _Tp = short int; std::allocator_traits<std::allocator<_Tp1> >::allocator_type = std::allocator<short int>]'
(...)include/c++/6.3.0/bits/vector.tcc:96:30: required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {long long unsigned int}; _Tp = short int; _Alloc = std::allocator<short int>]'
test.cpp:9:54: required from here
(...)include/c++/6.3.0/ext/new_allocator.h:120:4: error: conversion to 'short int' from 'long long unsigned int' may alter its value [-Werror=conversion]
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }
^
I know this is not a real solution, although it technically answers the question.
The problem is, that emplace_back
forwards all the arguments, in this case the uint64_t
to the constructor of the contained type. First the argument to emplace_back
is deduced as uint64_t
. No conversion happens at the call of emplace back. The narrowing conversion then happens "inside" the emplace_back
implementation in the system header. The compiler does not know that this is the fault of the caller and suppresses the warning because it is in a system header.
I solve this by templates and specialisation:
template<
typename T/*the desired type*/,
typename Y/*the source type*/
> T integral_cast(const Y& y)
{
static_assert(false, "undefined integral_cast");
}
which I then specialise at leisure if I want the cast to work:
// Pass through for uint32_t
template<>
inline std::uint32_t integral_cast(const uint32_t& y)
{
return y;
}
and
// Specialisation to convert std::uint32_t to double
template<>
inline double integral_cast(const std::uint32_t& y)
{
double ret = static_cast<double>(y); // this never loses precision under IEEE754
return ret;
}
At the point of use you write code of the form
int16_t y = integral_cast<int16_t>(std::numeric_limits<uint64_t>::max());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With