12

Is is possible to memcpy from a double array to a float array safely?

dmessf
  • 1,025
  • 3
  • 11
  • 19

5 Answers5

32

Depends on what you want. The values certainly won't be preserved. If you need that, use std::copy.

#include <algorithm>

int main()
{
    double a[] = {1.618, 3.1416, 2.7, 0.707, 1.0};
    float b[5];
    std::copy(a, a + 5, b);
}
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • This does not work (for me on VS2017) since the compiler does not allow the implicit loss of precision from double to float via `std::copy`. I had to use the `std::transform` function instead, using a lamda. This also clearer shows that an explicit loss-of-precision is actually taking place. – AzP May 31 '18 at 08:13
  • @AzP it sounds like you've chosen compiler options to make it reject valid code then. (Or there's a compiler bug) – M.M Dec 18 '18 at 22:13
  • 1
    @M.M well, implicit loss of precision is a bit dangerous - depending on what you are working with - and using a warning level of 4 will result in a compiler warning. Since I'm using 'treat warnings as errors' it won't compile. "Valid code" in this sense doesn't mean 'good code'. – AzP Dec 19 '18 at 10:40
4

The problem is that there is no guarantee that the compiler's binary representation of a double is the equivalent representation of a float. In order to use memcpy for multi-byte types is that the underlying representation must be the same (same layout). You can safely copy float to float, int to int and double to double.

You are destined for undefined behavior when the source type does not match the destination type, such as copying from long to char or float to double. The memcpy function does not make any conversion or perform any promotions. It just copies.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
3

Like many others have answered, using memcpy does not work since the two types are (generally) different in size. Please see more at http://en.cppreference.com/w/cpp/language/types, or more specifically:

Floating point types

float - single precision floating point type. Usually IEEE-754 32 bit floating point type

double - double precision floating point type. Usually IEEE-754 64 bit floating point type

long double - extended precision floating point type. Does not necessarily map to types mandated by IEEE-754. Usually 80-bit x87 floating point type on x86 and x86-64 architectures.

Using std::copy will give you a compiler warning (at least for me on the VS2015/VS2017 compiler) since the compiler does not allow the implicit loss of precision from double to float via std::copy, without warning the developer about it. And if you have the treat warnings as errors flag set, you will get a compiler error.

1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2316): error C2220: warning treated as error - no 'object' file generated
1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2335): note: see reference to function template instantiation '_OutIt std::_Copy_unchecked1<_InIt,_OutIt>(_InIt,_InIt,_OutIt,std::_General_ptr_iterator_tag)' being compiled
1>        with
1>        [
1>            _OutIt=float *,
1>            _InIt=double *
1>        ]
1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2354): note: see reference to function template instantiation '_OutIt *std::_Copy_unchecked<_InIt,float*>(_InIt,_InIt,_OutIt)' being compiled
1>        with
1>        [
1>            _OutIt=float *,
1>            _InIt=double *
1>        ]
1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2364): note: see reference to function template instantiation '_OutIt std::_Copy_no_deprecate1<double*,_OutIt>(_InIt,_InIt,_OutIt,std::random_access_iterator_tag,std::random_access_iterator_tag)' being compiled
1>        with
1>        [
1>            _OutIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<float>>>,
1>            _InIt=double *
1>        ]
1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2373): note: see reference to function template instantiation '_OutIt std::_Copy_no_deprecate<_InIt,_OutIt>(_InIt,_InIt,_OutIt)' being compiled
1>        with
1>        [
1>            _OutIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<float>>>,
1>            _InIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<double>>>
1>        ]
1>test.cpp(153): note: see reference to function template instantiation '_OutIt std::copy<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<double>>>,std::_Vector_iterator<std::_Vector_val<std::_Simple_types<float>>>>(_InIt,_InIt,_OutIt)' being compiled
1>        with
1>        [
1>            _OutIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<float>>>,
1>            _InIt=std::_Vector_iterator<std::_Vector_val<std::_Simple_types<double>>>
1>        ]
1>C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xutility(2316): warning C4244: '=': conversion from 'double' to 'float', possible loss of data

Instead, I recommend using use the std::transform function, combined with a lamda performing the specific cast. This also clearer shows that an explicit loss-of-precision is actually taking place.

std::vector<double> doubles = { 5.0, 10.0, 242.130, 42.0 };
std::vector<float> floats(doubles.size());
std::transform(std::begin(doubles), std::end(doubles), std::begin(floats), [&](const double& value) { return static_cast<float>(value); });
AzP
  • 1,081
  • 1
  • 16
  • 27
  • 1
    Your problem is this "treat warnings as errors" nonsensical option. Sure, `transform` works, is more explicit, and thus might be preferred in some code bases, but "does not work" for `copy` is clearly wrong. – Marc Glisse May 31 '18 at 08:42
  • 1
    @MarcGlisse, true, I'll change that wording. I disagree with the option being "nonsensical" though. – AzP May 31 '18 at 09:04
1

In general case - no.

In specific cases, on a given platform the representation of float and double might be the same, and the copy will succeed. But it doesn't make any practical sense anyway.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
1

memcpy is type agnostic (just sees bytes) and can't do type conversion. Just use std::transform as @AzP said:

std::transform(a, a + 5, b, [](double d) -> float {return float(d);});
wcochran
  • 10,089
  • 6
  • 61
  • 69