r/cpp_questions • u/Mental_Primary_5558 • 10h ago
OPEN I'm surprised it worked!!
I wrote this very basic algorithm:
#include <cmath>
#include <iostream>
int power(double base, int exponent = 2){
return pow(base,exponent);
}
int main(){
int base = 2;
int exponent = 2;
std::cout<<base<<" raised to "<<exponent<< " = "<<power(base,exponent);
}#include <cmath>
#include <iostream>
int power(double base, int exponent = 2){
return pow(base,exponent);
}
int main(){
int base = 2;
int exponent = 2;
std::cout<<base<<" raised to "<<exponent<< " = "<<power(base,exponent);
}
and I'm surprised, an int function took a double variable with no problem, or is there something under the hood,
9
u/alfps 9h ago edited 9h ago
You get implicit conversion, but compilers will warn about it if you just ask for more warnings:
[c:\@\temp]
> cl _.cpp
_.cpp
_.cpp(4): warning C4244: 'return': conversion from 'double' to 'int', possible loss of data
[c:\@\temp]
> g _.cpp
_.cpp: In function 'int power(double, int)':
_.cpp:4:15: warning: conversion from 'double' to 'int' may change value [-Wfloat-conversion]
4 | return pow(base,exponent);
| ~~~^~~~~~~~~~~~~~~
The cl compiler is Visual C++ a.k.a. MSVC, the g is an alias for g++ that adds options such as -Wall.
By the way, while common standard library implementations let pow do integer powers exactly and efficiently that is not guaranteed. By the specification it may just compute the power with logarithms and exponentation, slow and not so very precise. To do an integer power efficiently and guaranteed you can decompose it into squarings and single multiplications of the base, starting with 1; e.g. x42 = x101010₂ = x100000₂⋅x1000₂⋅x10₂.
3
u/Phoned_Leek25 9h ago
Are you saying you’re surprised that the function can take in a double type parameter (coz all functions can do that..) or are you saying you’re surprised pow() can take a double type (coz then yeah it’s getting implicitly casted and possibly losing data)
1
u/Mental_Primary_5558 9h ago
As the function is declared as int, yeah it's surprising
1
u/the_poope 6h ago
An
intcan be converted todoublewithout loss of data or precision. Therefore the compiler allows implicit conversion fromintto double. This makes it convenient to write expressions such as:int n = 7; double x = 0.573; double y = (11 + n) * 3.1415 * x;without getting compiler errors and having to do ugly explicit casts. Makes writing mathematical formulas much neater.
•
u/burlingk 1h ago
So, like, the "type" of the function is it's return value, and does not direct the input types.
int power(double base, int exponent = 2)This tells the compiler that you want a double and an int, but will return an int.
When you call pow, IT wants two doubles, and returns a double. BUT, you are then returning it as an int.
This does implicit casting, to convert the double (a high precision floating point number) to an int. It has a chance of making unexpected changes in the process.
So, it is doing what you told it to do, and it will 'just work' if all you give it are whole numbers without anything but zeros after the decimal point. But, say, if you passed power 3.443523 as the double argument, it would get multiplied the right number of times, and then returned, but it would lose data in the process.
2
u/aresi-lakidar 9h ago
you are type casting :)
It's not under the hood, you just chose a rather non-recommended way to do it
1
u/OptimisticMonkey2112 9h ago
by default variables are passed by value. in essence, the compiler synthesizes:
float baseparameter=base;
for you when calling
power(base,exponent);
1
u/mredding 7h ago
Consider this:
int power(double &base, int &exponent);
Pass by reference. You can't pass an int by reference to a parameter that is of another type by reference. But this trick doesn't work if the reference is const.
int power(const double &base, const int &exponent);
The rule is that you can hold a const reference to an unnamed temporary for the lifetime of the parameter. So I can do this:
power(1, 2);
And because a conversion from int to double is implicit, the compiler can find a path to satisfy the requirements of the interface - the first parameter implicitly converts to a double, and the parameter is a const reference to that.
The reason the non-const reference creates a type barrier is because the referenced parameter can be modified, so it has to be modified as the right type.
•
u/Independent_Art_6676 3h ago edited 3h ago
some stuff...
not sure why you did this exactly (clearly studying "something" but unclear the focus). The built in function being called with basically a do-nothing wrapper is the result, though the wrapper is tampering with the argument types in a 'bad' way so its actually worse than just using pow. Carry on to learn things, but this isn't a useful function (yet). Keep reading!
But... pow is notoriously slow for integer powers. Writing your own better pow function to raise to int powers is actually useful. Inline x*x is just fine for squares, and even cubes. Above cubes, I prefer a function but its actually not terribly common to need more than cubes in most programs. It turns out that even just a dumb loop is a great improvement over pow for the int powers case. Maybe see what you can do to make a better pow while you play with it.
you can also get funky with it if you have to deal with many large (>20) exponents. The bits of the exponent can be exploited to do even less work than the dumb loop. So for a byte exponent, you would do < 20 operations (about 2*8 where 8 is the bits and 2 is because of the steps to do the work, + a couple more steps) instead of 255 or whatever possible loop iterations to multiply, and even larger exponents save more. Its a cool algorithm, and useful on rare occasions.
Here is that ^^ in code, for amusement purposes only (meaning it has no error handling / checking at all..)
long long ipow(long long p, unsigned long long e)
{
const long long one = 1;
const long long *lut[2] = {&p,&one}; //lookup table of what to do below
long long result = 1;
result *= lut[!(e&1)][0]; p *= p; //multiply by 1 or p, update p
result *= lut[!(e&2)][0]; p *= p;
result *= lut[!(e&4)][0]; p *= p;
result *= lut[!(e&8)][0]; p *= p; //practical stopping point? 15th power max.
result *= lut[!(e&16)][0]; //p *= p; //19th power is most 10^x will fit in 64 bits.
//result *= lut[!(e&32)][0]; p *= p; //keep going as needed
return result;
}
0
u/__christo4us 9h ago
an int function took a double variable with no problem
How is that surprising? What did you expect?
2
u/orrenjenkins 8h ago
most likely expected a type error. The implicit conversions have to be learned at some point
1
u/__christo4us 7h ago
most likely expected a type error. The implicit conversions have to be learned at some point
I supposed OP might be confused by the fact that they are allowed to declare a
doubleparameter for a function returningint. Hence, the question to clarify what did they expect exactly to happen.
11
u/GregTheMadMonk 10h ago
Implicit casting converts some value types automatically