Reputation: 610
I'm trying to understand something about sin
and sinf
from math.h
.
I understand that their types differ: the former takes and returns double
s, and the latter takes and returns float
s.
However, GCC still compiles my code if I call sin
with float
arguments:
#include <stdio.h>
#include <math.h>
#define PI 3.14159265
int main ()
{
float x, result;
x = 135 / 180 * PI;
result = sin (x);
printf ("The sin of (x=%f) is %f\n", x, result);
return 0;
}
By default, all compiles just fine (even with -Wall
, -std=c99
and -Wpedantic
; I need to work with C99). GCC won't complain about me passing floats to sin
. If I enable -Wconversion
then GCC tells me:
warning: conversion to ‘float’ from ‘double’ may alter its value [-Wfloat-conversion]
result = sin (x);
^~~
So my question is: is there a float
input for which using sin
, like above, and (implicitly) casting the result back to float
, will result in a value that is different from that obtained using sinf
?
Upvotes: 1
Views: 1030
Reputation: 81179
There are some systems where computations on float
are an order of magnitude faster than computations on double
. The primary purpose of sinf
is to allow trigonometric calculations to be performed efficiently on such systems in cases where the lower precision of float
would be adequate to satisfy application needs. Converting a value to float
, calling sin
, and converting the result to float
would always yield a value that either matched that of sinf
or was more accurate(*), and on some implementations that would in fact be the most efficient way of implementing sinf
. On some other systems, however, such an approach would be more than an order of magnitude slower than using a purpose-designed function to evaluate the sine of a float
.
(*) Note that for arguments outside the range +/- π/2, the most mathematically accurate way of computing sin(x) for an exact specified value of x might not be the most accurate way of computing what the calling code wants to know. If an application computes sinf(angle * (2.0f * 3.14159265f))
, when angle
is 0.5, having the function (double)3.1415926535897932385-(float)3.14159265f
may be more "mathematically accurate" than having it return sin(angle-(2.0f*3.14159265f)), but the latter would more accurately represent the sine of the angle the code was actually interested in.
Upvotes: 0
Reputation: 12668
However, GCC still compiles my code if I call sin with float arguments:
Yes, this is because they are implicitly converted to double
(because sin()
requires a float), and back to float
(because sin()
returns a double
) on entering and exiting from the sinf()
function. See below why it is better to use sinf()
in this case, instead of having only one function.
You have included math.h
which has prototypes for both function calls:
double sin(double);
float sinf(float);
And so, the compiler knows that to use sin()
it is necessary a conversion from float
to double
so it compiles a conversion before calling, and also compiles a conversion from double
to float
in the result from sin()
.
In case you have not #include <math.h>
and you ignored the compiler warning telling you are calling a function sin()
with no prototype, the compiler should have also converted first the float
to double
(because on nonspecified argument types this is how it mus proceed) and pass the double
data to the function (which is assumed to return an int
in this case, that will provoke a serious Undefined Behaviour)
In case you have used the sinf()
function (with the proper prototype), and passed a float
, then no conversion should be compiled, the float is passed as such with no type conversion, and the returned value is assigned to a float
variable, also with no conversion. So everything goes fine with no conversion, this makes the fastest code.
In case you have used the sinf()
function (with no prototype), and passed a float
, this float would be converted to a double
and passed as such to sinf()
, resulting in undefined behaviour. In case somehow sinf()
returned properly, an int
result (that could have something to do with the calculation or not, as per UB) would be converted into float
type (should this be possible) and assigned to the result value.
In the case mentioned above, in case you are operating on float
s, it is better to use sinf()
as it takes less to execute (it has less iterations to do, as less precision is required in them) and the two conversions (from float
to double
and back from double
to float
) have not to be compiled in, in the binary code output by the compiler.
Upvotes: 0
Reputation: 17403
This will find all the float input
values in the range 0.0
to 2 * M_PI
where (float)sin(input) != sinf(input)
:
#include <stdio.h>
#include <math.h>
#include <float.h>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
int main(void)
{
for (float in = 0.0; in < 2 * M_PI; in = nextafterf(in, FLT_MAX)) {
float sin_result = (float)sin(in);
float sinf_result = sinf(in);
if (sin_result != sinf_result) {
printf("sin(%.*g) = %.*g, sinf(%.*g) = %.*g\n",
FLT_DECIMAL_DIG, in, FLT_DECIMAL_DIG, sin_result,
FLT_DECIMAL_DIG, in, FLT_DECIMAL_DIG, sinf_result);
}
}
return 0;
}
There are 1020963 such inputs on my amd64 Linux system with glibc 2.32.
Upvotes: 4
Reputation: 93476
float
precision is approximately 6 significant figures decimal, while double
is good for about 15. (It is approximate because they are binary floating point values not decimal floating point).
As such for example: a double
value 1.23456789
will become 1.23456xxx
as a float
where xxx
are unlikely to be 789
in this case.
Clearly not all (in fact very few) double
values are exactly representable by float
, so will change value when down-converted.
So for:
double a = 1.23456789 ;
float b = a ;
printf( "double: %.10f\n", a ) ;
printf( "float: %.10f\n", b ) ;
The result in my test was:
double: 1.2345678900
float: 1.2345678806
As you can see the float
in fact retained 9 significant figures in this case, but it is by no means guaranteed for all possible values.
In your test you have limited the number of instances of mismatch because of the limited and finite range of rand()
and also because f
itself is float
. Consider:
int main()
{
unsigned mismatch_count = 0 ;
unsigned iterations = 0 ;
for( double f = 0; f < 6.28318530718; f += 0.000001)
{
float f1 = sinf(f);
float f2 = sin(f);
iterations++ ;
if(f1 != f2)
{
mismatch_count++ ;
}
}
printf("%f%%\n", (double)mismatch_count/iterations* 100.0);}
In my test about 55% of comparisons mismatched. Changing f
to float
, the mismatches reduced to 1.3%.
So in your test, you see few mismatches because of the constraints of your method of generating f
and its type. In the general case the issue is much more obvious.
In some cases you might see no mismatches - an implementation may simply implement sinf()
using sin()
with explicit casts. The compiler warning is for the general case of implicitly casting a double
to a float
without reference to any operations performed prior to the conversion.
Upvotes: 0
Reputation: 47952
This program finds three examples on my machine:
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
float f, f1, f2;
for(i = 0; i < 10000; i++) {
f = (float)rand() / RAND_MAX;
float f1 = sinf(f);
float f2 = sin(f);
if(f1 != f2) printf("jackpot: %.8f %.8f %.8f\n", f, f1, f2);
}
}
I got:
jackpot: 0.98704159 0.83439910 0.83439904
jackpot: 0.78605396 0.70757037 0.70757031
jackpot: 0.78636044 0.70778692 0.70778686
Upvotes: 3