Solution to scanf 9.9999999 as a float ?


Linux-SWAT

Hardcore Member
Joined
Feb 13, 2010
Messages
8,531
Hi,

9.9999999 is stored as 10.000000 when using scanf.

How to get the right value, loosing a minimum of precision ?
 

levi

Still fresh, damnit!
Joined
Oct 6, 2008
Messages
12,853
Location
Somewhere off the coast of the EU
What are you scanning it into? Single precision floats only have a precision of 24 bits, and by my reckoning, that value is 27 bits long. Use a double instead of a float and you might have more success.
 

kuru

Laptop und Trachtenjanker
Joined
Oct 8, 2008
Messages
2,941
Location
the mockracy
Maybe read as string, cut decimals that would cause rounding error, then do numbery things with it

Edit: this also allows for sanity checking the input, e.g. comma instead of point
 
Last edited:

slaeshjag

¯\_(ツ)_/¯
Joined
Apr 8, 2010
Messages
2,687
Location
~Stockholm, Sweden
If you can't live with that loss of precision, especially with a double, you should really re-evaluate what you're spending your time on.

If you really want an as-accurate-as-cumputers-could-possibly-get representation of a decimal fraction, you'll need to use something like libmpfr.
 

levi

Still fresh, damnit!
Joined
Oct 6, 2008
Messages
12,853
Location
Somewhere off the coast of the EU
Yeah, there will always be a limit to what numbers you can store. But does it really matter? 10-9.9999999 is only out by a tenth of a millionth, in decimal. Sometimes you do need more and a double will get you twice the resolution. Beyond that you'll need a library, but you'll lose any real kind of hardware support for non-standard floating point numbers.

@TeDaDeS That seems to be to do with the resolution as displayed to the end user. That's still a concern if you're just dumping out the number to terminal at the end, but I think the problem here is just storing the number in the first place; the number given is just too long for a single precision float.
 

Linux-SWAT

Hardcore Member
Joined
Feb 13, 2010
Messages
8,531
What annoys me is that a >= 10 test will be ok when a user entered 9.9999999...
 

levi

Still fresh, damnit!
Joined
Oct 6, 2008
Messages
12,853
Location
Somewhere off the coast of the EU
Mathematically, 9 with enough .9s* after is is entirely equal to 10 though. I guess for your purposes you'd want to have it as a string and test it there before converting it to some appropriate floating point number. Split on the decimal point (after checking it's only got one) and the bit before will be your rounded down integer part, and you could convert that to an int and test it. Any time you put it in a floating point value, it'll try to minimise errors and anything with a final digit of 5 and above and more precise than the floating point value will always be rounded to the value above, in this case 10.000000.

* enough .9s: technically you need infinite .9s for it to be 10, but it always tends towards 10 as you keep adding .9s at least.
 

mjohansson

Supporter
Joined
Feb 10, 2011
Messages
409
Mayby try capturing the input one number by one as int and add it to a float manually, like something like this:
while(){
scanf("%d",&i);
floatvar += (float)i * x;
x*= 0.1f;
}
actually you might not want to use scanf for such a routine but just check what buttons was pressed on keyboard, it would be a bit more code to deal with if you havent the system for it in place already...
[doublepost=1488642549,1488637937][/doublepost]I made an example program, its not complete but will demo the basic of what I meant.

Code:
#include "SDL/SDL.h"
#include <iostream>
using namespace std;

int key[256]={0};
#define k_1 26
#define k_2 27
#define k_3 28
#define k_4 29
#define k_5 30
#define k_6 31
#define k_7 32
#define k_8 33
#define k_9 34
#define k_0 35
#define k_ESCAPE 40
#define k_RETURN 43

void sgUPDATE_KEYBOARD_STATE(){const Uint8* currentKeyStates;
currentKeyStates = SDL_GetKeyState( NULL );
if( currentKeyStates[SDLK_1] ){key[k_1]=1;}else{key[k_1]=0;}
if( currentKeyStates[SDLK_2] ){key[k_2]=1;}else{key[k_2]=0;}
if( currentKeyStates[SDLK_3] ){key[k_3]=1;}else{key[k_3]=0;}
if( currentKeyStates[SDLK_4] ){key[k_4]=1;}else{key[k_4]=0;}
if( currentKeyStates[SDLK_5] ){key[k_5]=1;}else{key[k_5]=0;}
if( currentKeyStates[SDLK_6] ){key[k_6]=1;}else{key[k_6]=0;}
if( currentKeyStates[SDLK_7] ){key[k_7]=1;}else{key[k_7]=0;}
if( currentKeyStates[SDLK_8] ){key[k_8]=1;}else{key[k_8]=0;}
if( currentKeyStates[SDLK_9] ){key[k_9]=1;}else{key[k_9]=0;}
if( currentKeyStates[SDLK_0] ){key[k_0]=1;}else{key[k_0]=0;}
if( currentKeyStates[SDLK_ESCAPE] ){key[k_ESCAPE]=1;}else{key[k_ESCAPE]=0;}
if( currentKeyStates[SDLK_RETURN] ){key[k_RETURN]=1;}else{key[k_RETURN]=0;}
}

SDL_Event sgEvent;
int sgPROCESS_EVENT(){int status=0;
while(SDL_PollEvent(&sgEvent)){
 if(sgEvent.type == SDL_QUIT){status=2;}}
sgUPDATE_KEYBOARD_STATE();return status;}

#define sgDefineStandardMaxFPS 50.0f
Uint32 sgOldTime=0,sgCurrentTime=0;
float sgTimeUnit=0.1f,sgFrameRate=1000.0f/sgDefineStandardMaxFPS;

void sgGET_TIME(Uint32& utime){utime=SDL_GetTicks();}
void sgDELAY_TIME(Uint32 utime){SDL_Delay(utime);}

void sgUPDATE_TIME(){float x=0.0f;Uint32 td=0;sgGET_TIME(sgCurrentTime);
 if(sgOldTime>sgCurrentTime){sgCurrentTime=sgOldTime;}x=sgCurrentTime-sgOldTime;
 if(x<sgFrameRate){x=sgFrameRate-x;td=x;sgDELAY_TIME(td);sgCurrentTime+=td;}
 sgTimeUnit=(float)(sgCurrentTime-sgOldTime)/1000.0f;
 if(sgTimeUnit<0.0f){sgTimeUnit*=-1.0f;}sgOldTime=sgCurrentTime;}

int sgQuit=0;
SDL_Surface *sgWindow;
#define sgFrameStart 0
#define sgFrameEnd 1
void sgFRAME_ROUTINE(int id){int sgInfo;
if(id==sgFrameStart){
  sgInfo=sgPROCESS_EVENT();if(sgInfo==2){sgQuit=1;}}
if(id==sgFrameEnd){
  sgUPDATE_TIME();
  SDL_Flip(sgWindow);
}
}


int main(int argc,char **argv){
int k_0h=0,k_1h=0,k_2h=0,k_3h=0,k_4h=0,k_5h=0,k_6h=0,k_7h=0,k_8h=0,k_9h=0;
float x=1.0f,floatvar=0.0f;
SDL_Init(SDL_INIT_EVERYTHING);
sgWindow=SDL_SetVideoMode(250,250,16,SDL_SWSURFACE);

while(sgQuit==0){//**************************************** GAME LOOP
sgFRAME_ROUTINE(sgFrameStart);

if(key[k_ESCAPE]==1){sgQuit=1;cout<<endl<<"exit ESC "<<floatvar<<endl;}

if(key[k_0]==0){k_0h=0;}
if(key[k_1]==0){k_1h=0;}
if(key[k_2]==0){k_2h=0;}
if(key[k_3]==0){k_3h=0;}
if(key[k_4]==0){k_4h=0;}
if(key[k_5]==0){k_5h=0;}
if(key[k_6]==0){k_6h=0;}
if(key[k_7]==0){k_7h=0;}
if(key[k_8]==0){k_8h=0;}
if(key[k_9]==0){k_9h=0;}

if(key[k_0]==1&&k_0h==0&&x>0.000001f){k_0h=1;floatvar+=0.0f*x;x*=0.1f;cout<<0;if(x==0.1f){cout<<".";}}
if(key[k_1]==1&&k_1h==0&&x>0.000001f){k_1h=1;floatvar+=1.0f*x;x*=0.1f;cout<<1;if(x==0.1f){cout<<".";}}
if(key[k_2]==1&&k_2h==0&&x>0.000001f){k_2h=1;floatvar+=2.0f*x;x*=0.1f;cout<<2;if(x==0.1f){cout<<".";}}
if(key[k_3]==1&&k_3h==0&&x>0.000001f){k_3h=1;floatvar+=3.0f*x;x*=0.1f;cout<<3;if(x==0.1f){cout<<".";}}
if(key[k_4]==1&&k_4h==0&&x>0.000001f){k_4h=1;floatvar+=4.0f*x;x*=0.1f;cout<<4;if(x==0.1f){cout<<".";}}
if(key[k_5]==1&&k_5h==0&&x>0.000001f){k_5h=1;floatvar+=5.0f*x;x*=0.1f;cout<<5;if(x==0.1f){cout<<".";}}
if(key[k_6]==1&&k_6h==0&&x>0.000001f){k_6h=1;floatvar+=6.0f*x;x*=0.1f;cout<<6;if(x==0.1f){cout<<".";}}
if(key[k_7]==1&&k_7h==0&&x>0.000001f){k_7h=1;floatvar+=7.0f*x;x*=0.1f;cout<<7;if(x==0.1f){cout<<".";}}
if(key[k_8]==1&&k_8h==0&&x>0.000001f){k_8h=1;floatvar+=8.0f*x;x*=0.1f;cout<<8;if(x==0.1f){cout<<".";}}
if(key[k_9]==1&&k_9h==0&&x>0.000001f){k_9h=1;floatvar+=9.0f*x;x*=0.1f;cout<<9;if(x==0.1f){cout<<".";}}

if(x<0.00001f){sgQuit=1;cout<<endl<<"exit END "<<floatvar<<endl;}

sgFRAME_ROUTINE(sgFrameEnd);}//**************************** END OF GAME LOOP
SDL_Quit();
return 0;}
 

Linux-SWAT

Hardcore Member
Joined
Feb 13, 2010
Messages
8,531
Nice.

I wondered the other day if it's possible to do a C for with an iterated variable like in bash, i guess not :
Code:
for (i=0; i<=9; ++i) {
if(key[k_$i]==1&&k_$ih==0&&x>0.000001f){k_$ih=1;floatvar+=$i.0f*x;x*=0.1f;cout<<$i;if(x==0.1f){cout<<".";}}
}
 
Last edited:

Caine

Hardcore Member
Joined
Jun 5, 2008
Messages
4,138
Location
Netherlands
Let 0.9999999... equal a
then 10a = 9.999999...
subtract a left and right: 9a = 9
thus a = 1

Naturally, in this case we have a finite number of 9's, so it doesn't entirely work.
 

levi

Still fresh, damnit!
Joined
Oct 6, 2008
Messages
12,853
Location
Somewhere off the coast of the EU
That linked question is too dumb. Scanning it into a float has reduced the precision beyond the resolution needed, so once you limit the output resolution it rounds it back to the right decimal value.

Since the value 9.9999999 is beyond the resolution of a 32-bit float, I strong suspect that was the original problem. If Linux-SWAT apparently wants infinite resoltion, you have to stop using limited data structures like float and double.
 

levi

Still fresh, damnit!
Joined
Oct 6, 2008
Messages
12,853
Location
Somewhere off the coast of the EU
Sure, but for that test you need infinite resolution, unless you use a way to enter the integer part separately to the fraction, either like I suggested and parsing a string into a float, or mjohanssons's solution which interactively generates a number based on user input, and therefore can look at the digits as they come in.

That's unless there's a way to make float() constructor round down, or scanf. I don't know of a way, but I've not coded C for many years now.
 

rohezal

Advanced Member
Joined
Oct 18, 2009
Messages
1,678
You can cut of the numbers behind the 9.99! Look for the dot and set insert 2 numbers behind it a terminating zero. Dirty but working.

Code:
char input[64];
gets_s( input, 64);

int i = 0;

while(input[i] != '.' && i < 64-3)
{
i++;
}

input[i+3] = 0; //binary 0, Ends the string

float f = atof(input);
 
Top