Real/LONG values
-
On 16/01/2014 at 23:30, xxxxxxxx wrote:
User Information:
Cinema 4D Version: R14
Platform: Windows ;
Language(s) : C++ ;---------
Hi Folks,lengthy post follows!
I'm wondering if someone could shed some light on the following scenario that I find appears quite often when dealing with Reals and LONGS.
Here's an example (with some GePrint'ed values I copied over below to show the problem). Think of a timeline, with a frame rate of 25 frames per second (this makes the millisecond rounding easy to work with for this example, at 4ms per frame).
I incremenet the CURRENTFRAME value inside a dialog's Timer() function, but for the purpose of this it's not necessary to set up. There's an example function at the bottom that works without the timer.
Now, below is some prints of what are happening each time the CURRENTFRAME is incremented. Just about everything prints what it should be, except that every now and then there's a frame whose equivalent timecode-based value is NOT correct. See the prints for CURRENTFRAME 29 and 30 below.
CURRENTFRAME = 25 // CORRECT SEC = 1 String SEC = 1 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 0 LEFTOVER = 0 CURRENTFRAME = 26 // CORRECT SEC = 1.04 String SEC = 1.04 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 4 LEFTOVER = 0.04 CURRENTFRAME = 27 // CORRECT SEC = 1.08 String SEC = 1.08 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 8 LEFTOVER = 0.08 CURRENTFRAME = 28 // CORRECT SEC = 1.12 String SEC = 1.12 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 12 LEFTOVER = 0.12 CURRENTFRAME = 29 // WRONG SEC = 1.16 String SEC = 1.16 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 15 LEFTOVER = 0.16 CURRENTFRAME = 30 // WRONG SEC = 1.2 String SEC = 1.2 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 19 LEFTOVER = 0.2 CURRENTFRAME = 31 // CORRECT SEC = 1.24 String SEC = 1.24 HOURS = 0 MINUTES = 0 SECONDS = 1 MILLISECONDS = 24 LEFTOVER = 0.24
The MILLISECONDS value is taken by getting the LEFTOVER value, and multplying that by 100 and removing all the equivalent seconds value. So, in the case of frame 30, the MILLISECOND value should be 20, but it is not, it's 19. I'm aware that LONG's don't take decimal places, and use this often to get rid of decimal places and do some number shuffling at times with these. It normally works quite well. But in the case of above, it's just plain wrong.
The following is a custom function I put together of how the GePrints etc above come about. Would someone be so kind as to have a look, or pop it into a plugin of their own to try and see what values you get (maybe call from a button/command)?
void TestPrints(void) { Real FRAMERATE; LONG FRAMECOUNT; Real CURRENTFRAME; Real SEC; LONG HOURS; LONG MINUTES; LONG SECONDS; LONG MILLISECONDS; Real LEFTOVER; BaseTime Time; CURRENTFRAME = 29.0; FRAMERATE = 25.0; Time.SetDenominator(FRAMERATE); Time.SetNumerator(CURRENTFRAME); SEC = Time.Get(); HOURS = SEC/3600; if(HOURS < 0){HOURS = 0;} MINUTES = (SEC-(HOURS*60))/60.0; if(MINUTES < 0){MINUTES = 0;} SECONDS = (SEC - (HOURS*3600) - (MINUTES*60)); if(SECONDS < 0){SECONDS = 0;} LEFTOVER = (SEC - ((HOURS*3600) + (MINUTES*60) + SECONDS)); MILLISECONDS = LEFTOVER*100; if(MILLISECONDS < 0){MILLISECONDS = 0;} GePrint("CURRENTFRAME = " + RealToString(CURRENTFRAME)); GePrint("SEC = " + RealToString(SEC)); String str = FormatNumber(SEC,FORMAT_REAL,NULL,FALSE);GePrint("String SEC = " + str); GePrint("HOURS = " + LongToString(HOURS)); GePrint("MINUTES = " + LongToString(MINUTES)); GePrint("SECONDS = " + LongToString(SECONDS)); GePrint("MILLISECONDS = " + LongToString(MILLISECONDS)); GePrint("LEFTOVER = " + RealToString(LEFTOVER)); GePrint(" " ); }
Why does this simple calculation not work correctly!?
Cheers,
WP.
-
On 17/01/2014 at 04:36, xxxxxxxx wrote:
Hi,
please that at least in C++ ALL_CAPS are used for preprocessor detectives and not for variable names.
What you sees is just floating point behavior.
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
http://floating-point-gui.de/You have also a lot of implicit conversion from 'double' to 'LONG'.
For example if you assign almost 2 to LONG you will get 1 instate. (per default)
LONG i = 1.9999; //now i is 1
You should at least use Round function to convert Real to LONG like below.LONG Round(Real x) { return Floor(x + 0.5); }
void TestPrints(void) { Real framerate; //LONG framecount; Real currentframe; Real sec; LONG hours; LONG minutes; LONG seconds; LONG milliseconds; Real leftover; BaseTime time; currentframe = 29.0; framerate = 25.0; time.SetDenominator(framerate); time.SetNumerator(currentframe); sec = time.Get(); hours = sec / 3600.0;//implicit conversion to integer if (hours < 0){ hours = 0; } minutes = (sec - (hours * 60.0)) / 60.0;//implicit conversion to integer if (minutes < 0){ minutes = 0; } seconds = (sec - (hours * 3600.0) - (minutes * 60.0));//implicit conversion to integer if (seconds < 0){ seconds = 0; } leftover = (sec - ((hours * 3600.0) + (minutes * 60.0) + seconds)); milliseconds = leftover * 100.0; //implicit conversion to integer if (milliseconds < 0){ milliseconds = 0; } GePrint("currentframe = " + RealToString(currentframe)); GePrint("sec = " + RealToString(sec)); String str = FormatNumber(sec, FORMAT_REAL, NULL, FALSE); GePrint("String SEC = " + str); GePrint("hours = " + LongToString(hours)); GePrint("minutes = " + LongToString(minutes)); GePrint("seconds = " + LongToString(seconds)); GePrint("milliseconds = " + LongToString(milliseconds)); GePrint("leftover = " + RealToString(leftover)); GePrint(" "); }
Remo
-
On 17/01/2014 at 17:51, xxxxxxxx wrote:
Hi Remo,
thanks for taking the time to have a look. I'm aware of the rounding that happens with LONGs, and have been using it to remove decimal values for some time. I perhaps should be using the Round() instead of the LONG and copying it back, but either way it still works (or certainly has in other areas previously).
The problem I'm getting though, is that inputting the same numbers (both decimal and rounded) into a calculator gives me the correct result both times, but in code running in a plugin it does not.
I'll need to have a read of the floating point link you've provided Remo just to check over. But there's clearly something I'm not getting in the printed values. I chose 25 frames per second because each frame pops up at 4ms interval. 4 goes into 100 without any leftovers. Why then does millisecond end up at 15, as 4 does not go into 15 (not without decimal places anyway). The Get() call on the BaseTime is returning 1.16, but after the leftover is worked out and turned into a ms and passed to milliseconds (which for this purpose I thought was 0.16*100), I'm left with 15, not 16. It's a very simple mathematical calculation that is either hampered by poor internal computer calculations, or (and quite possibly!) there's something about the decimal value Reals I'm still not seeing/grasping. But to me the math is simple - the Get() value in this case can only ever return something to two decimal places at most (due to the nature of 25fps), yet this does not seem to be the case here.
Before I go any further, how can I get and print a float value? Or is this what a Real is and what FormatNumber() do!?
WP.
-
On 19/01/2014 at 08:29, xxxxxxxx wrote:
Just an observation.
This is not the same
time.SetDenominator(framerate); time.SetNumerator(currentframe);
as this
time = BaseTime(currentframe,framerate);
-
On 19/01/2014 at 18:00, xxxxxxxx wrote:
Interesting, though I wonder why it wouldn't be the same? I just changed the code to try that Remo out of interest, but I ended up with the same incorrect printed results.
One other thing I also tried was multiplying the leftover value by 1000 instead of 100 to see if that could shed any light on other hidden decimal places. The value it gave me was 159 (0.16*1000 = 159..??). So there's something fishy to me going on behind the scenes that I can't see. The leftover value should have been 0.159 if that be the case, but neither the FormatNumber() or RealToString() printed that value (even though it's wrong).
I'm going to have a bit of a further play with the *1000 way. Might be able make some sort of fail safe out of it. But never-the-less, I'm quite struck at observing such a seemingly simple calculation end up like it does. I would have thought in this day and age computers could handle this quite comfortably...
WP.
-
On 19/01/2014 at 19:57, xxxxxxxx wrote:
OK, I'm genuinely quite stumped on this. When I change the millisecond to a float value, it works as expected (the printed numbers are then correct).
Is someone from Maxon able to provide some insight into why the Real values are not kosher in the above/below example?
Put the following functions into a plugin and run them one after the other. The printed millisecond values (see last CODE block) are just not the same. Please disregard all the comments and double-lining etc, I've done a bit of fiddling and tweaking and have had enough of doing that with this one =), so things might not sit correctly - but it should serve it's purpose here.
void TestPrints_Real(void) { Real framerate; //LONG framecount; Real currentframe; Real sec = 0.0; LONG hours; LONG minutes; Real seconds; Real milliseconds; Real leftover = 0.0; BaseTime time; currentframe = 29.0; framerate = 25.0; time = BaseTime(currentframe,framerate); //time.SetDenominator(framerate); //time.SetNumerator(currentframe); sec = time.Get(); LONG sec_L = sec; //Real t_sec = sec*1000.0; //GePrint("t_sec = " + RealToString(t_sec)); //String hub_bub = FormatNumber(t_sec,FORMAT_REAL,NULL,FALSE); //GePrint("String t_sec = " + hub_bub); ////LONG sec_t = t_sec; ////Real sdg = sec_t; ////Real bleh = sdg/1000; ////sec = bleh; hours = sec_L / 3600.0;//implicit conversion to integer if (hours < 0){ hours = 0; } minutes = (sec_L - (hours * 60.0)) / 60.0;//implicit conversion to integer if (minutes < 0){ minutes = 0; } seconds = (sec_L - (hours * 3600.0) - (minutes * 60.0));//implicit conversion to integer if (seconds < 0){ seconds = 0; } Real blip = sec-sec_L; GePrint("blip = " + RealToString(blip)); leftover = blip;//(sec - ((hours * 3600.0) + (minutes * 60.0) + seconds)); milliseconds = leftover * 100000.0; //implicit conversion to integer if (milliseconds < 0){ milliseconds = 0; } GePrint("currentframe = " + RealToString(currentframe)); GePrint("sec = " + RealToString(sec)); String str = FormatNumber(sec, FORMAT_REAL, NULL, FALSE); GePrint("String SEC = " + str); GePrint("hours = " + LongToString(hours)); GePrint("minutes = " + LongToString(minutes)); GePrint("seconds = " + LongToString(seconds)); GePrint("milliseconds = " + LongToString(milliseconds)); String str2 = FormatNumber(milliseconds, FORMAT_REAL, NULL, FALSE); GePrint("String milliseconds = " + str2); GePrint("leftover = " + RealToString(leftover)); String str3 = FormatNumber(leftover,FORMAT_REAL,FALSE,FALSE); GePrint("String leftover = " + str3); //Real t = leftover*100; //milliseconds = Round(t); //GePrint("ROUNDED milliseconds = " + LongToString(milliseconds)); // String str4 = FormatNumber(milliseconds, FORMAT_REAL, NULL, FALSE); GePrint("ROUNDED String milliseconds = " + str4); //Real t_ms = 100/framerate; //LONG t_ms_L = t_ms*10; //Real t_ms_L_R = t_ms_L; //Real divided = milliseconds/t_ms_L_R; //Real divided_refresh = divided*10; ////LONG divi_1 = divided*10; //if(divided != t_ms) //{ // GePrint(" "); // GePrint("Failsafe reached:"); // GePrint("t_ms = " + RealToString(t_ms)); // GePrint("t_ms_L = " + LongToString(t_ms_L)); // GePrint("t_ms_L_R = " + RealToString(t_ms_L_R)); // GePrint("divided = " + RealToString(divided)); // GePrint("divided_refresh = " + RealToString(divided_refresh)); // //GePrint("Divided milliseconds = " + RealToString(divided)); //} //Real next_idea = sec*1000/framerate; //GePrint(" "); //GePrint("next_idea = " + RealToString(next_idea)); GePrint(" "); } void TestPrints_Float(void) { Real framerate; //LONG framecount; Real currentframe; Real sec = 0.0; LONG hours; LONG minutes; Real seconds; float milliseconds; Real leftover = 0.0; BaseTime time; currentframe = 29.0; framerate = 25.0; time = BaseTime(currentframe,framerate); //time.SetDenominator(framerate); //time.SetNumerator(currentframe); sec = time.Get(); LONG sec_L = sec; //Real t_sec = sec*1000.0; //GePrint("t_sec = " + RealToString(t_sec)); //String hub_bub = FormatNumber(t_sec,FORMAT_REAL,NULL,FALSE); //GePrint("String t_sec = " + hub_bub); ////LONG sec_t = t_sec; ////Real sdg = sec_t; ////Real bleh = sdg/1000; ////sec = bleh; hours = sec_L / 3600.0;//implicit conversion to integer if (hours < 0){ hours = 0; } minutes = (sec_L - (hours * 60.0)) / 60.0;//implicit conversion to integer if (minutes < 0){ minutes = 0; } seconds = (sec_L - (hours * 3600.0) - (minutes * 60.0));//implicit conversion to integer if (seconds < 0){ seconds = 0; } Real blip = sec-sec_L; GePrint("blip = " + RealToString(blip)); leftover = blip;//(sec - ((hours * 3600.0) + (minutes * 60.0) + seconds)); milliseconds = leftover * 100000.0; //implicit conversion to integer if (milliseconds < 0){ milliseconds = 0; } GePrint("currentframe = " + RealToString(currentframe)); GePrint("sec = " + RealToString(sec)); String str = FormatNumber(sec, FORMAT_REAL, NULL, FALSE); GePrint("String SEC = " + str); GePrint("hours = " + LongToString(hours)); GePrint("minutes = " + LongToString(minutes)); GePrint("seconds = " + LongToString(seconds)); GePrint("milliseconds = " + LongToString(milliseconds)); String str2 = FormatNumber(milliseconds, FORMAT_REAL, NULL, FALSE); GePrint("String milliseconds = " + str2); GePrint("leftover = " + RealToString(leftover)); String str3 = FormatNumber(leftover,FORMAT_REAL,FALSE,FALSE); GePrint("String leftover = " + str3); //Real t = leftover*100; //milliseconds = Round(t); //GePrint("ROUNDED milliseconds = " + LongToString(milliseconds)); // String str4 = FormatNumber(milliseconds, FORMAT_REAL, NULL, FALSE); GePrint("ROUNDED String milliseconds = " + str4); //Real t_ms = 100/framerate; //LONG t_ms_L = t_ms*10; //Real t_ms_L_R = t_ms_L; //Real divided = milliseconds/t_ms_L_R; //Real divided_refresh = divided*10; ////LONG divi_1 = divided*10; //if(divided != t_ms) //{ // GePrint(" "); // GePrint("Failsafe reached:"); // GePrint("t_ms = " + RealToString(t_ms)); // GePrint("t_ms_L = " + LongToString(t_ms_L)); // GePrint("t_ms_L_R = " + RealToString(t_ms_L_R)); // GePrint("divided = " + RealToString(divided)); // GePrint("divided_refresh = " + RealToString(divided_refresh)); // //GePrint("Divided milliseconds = " + RealToString(divided)); //} //Real next_idea = sec*1000/framerate; //GePrint(" "); //GePrint("next_idea = " + RealToString(next_idea)); GePrint(" "); }
and place these somewhere (in a button command or something) :
TestPrints_Real(); TestPrints_Float();
My printed values are:
// REAL blip = 0.16 currentframe = 29 sec = 1.16 String SEC = 1.16 hours = 0 minutes = 0 seconds = 1 milliseconds = 15999 --> INCORRECT String milliseconds = 16000 leftover = 0.16 String leftover = 0.16 // FLOAT blip = 0.16 currentframe = 29 sec = 1.16 String SEC = 1.16 hours = 0 minutes = 0 seconds = 1 milliseconds = 16000 --> CORRECT String milliseconds = 16000 leftover = 0.16 String leftover = 0.16
Hoping it's just me, but worry it's something far more fundamental. Regards,
WP.
-
On 20/01/2014 at 01:31, xxxxxxxx wrote:
Be aware that 'Real' is not the same as 'float'. In any recent version of Cinema, Real is defined as LReal, and that in turn is a double. The precision is therefore different and that explains the differences you see.
Try 'double milliseconds' as opposed to 'float milliseconds' and you should see the same results as for Real.
-
On 20/01/2014 at 05:50, xxxxxxxx wrote:
As spedler says. And, despite the extra work, I am so glad that Maxon has decided to change its base types by bit-size rather than let the particular system make the determination. There is an entire thread here somewhere about this issue (generic int that changes bit-size based on system and bit-size versus int32, etc.).
-
On 20/01/2014 at 11:18, xxxxxxxx wrote:
Hi,
Real is actually a double (8-bytes) and is more precise as float (SReal).
What you are doing all the time is to implicitly converting floating points values to integer values and then back again, usually this is bad thing.
Please also note that for longer calculation using floating points errors are accumulations pretty fast.
If you are dealing with floating points then 0.15999999 is "the same" as 0.1600000 only with a small error that can happens all the time.
This is why C4D uses BaseTime and not simple Real for time in seconds.Remo
-
On 20/01/2014 at 12:13, xxxxxxxx wrote:
Using new version of my C4D++
I was able to make this function that show and probably solves you problem.
As you can see the value is "159.999999999999910" this is almost 160.
As it seems such lines are "(sec - (hours * 3600.0) - (minutes * 60.0) - seconds)" is probably source of extra calculation error.
Any way this are usual problems with floating points arithmetics.The function below is still not really correct but better.
{ //inline LONG LRound(Real x) { return Floor(x + 0.5); } auto LRound = [](Real x) { return Floor(x + 0.5); }; Real currentframe = 29.0; for(currentframe = 10.0; currentframe<40.0; currentframe+=1) { Real framerate = 25.0; BaseTime time(currentframe,framerate); const Real sec = time.Get(); Real hours = Floor(sec / 3600.0); //Floor() Calculates the largest previous integer number. Real minutes = Floor(sec / 60.0) - (hours * 60.0); Real seconds = Floor(sec - (hours * 3600.0) - (minutes * 60.0)); Real leftover = (sec - (hours * 3600.0) - (minutes * 60.0) - seconds); Real milliseconds = (leftover * 1000.0); //Ceil() Calculates the smallest following integer number. LONG milliseconds_l = LRound(milliseconds); GePrint("currentframe = " + RealToString(currentframe,-1,6)); GePrint("sec = " + RealToString(sec,-1,6)); GePrint("hours = " + LongToString(hours)); GePrint("minutes = " + LongToString(minutes)); GePrint("seconds = " + LongToString(seconds)); GePrint("milliseconds = " + LongToString(milliseconds_l) +" "+ RealToString(milliseconds,-1,15)); GePrint("leftover = " + RealToString(leftover,-1,6)); GePrint("------------------------"); } }
Remo
-
On 22/01/2014 at 18:51, xxxxxxxx wrote:
Thanks for the input all. I have a bit to read up on about these floats/doubles etc by the looks of it!
From a non-coders perspective, I'm quite struck to see potential numerical discrepancies like these in such a simple example. I've since done some playing about with LONGs, Reals, ints, floats etc and have found some strange behaviour in processing numbers at times. The kind of thing where 60 != 60.0!! I'm a little surprised that modern day architecture can't handle these things, but never-the-less workarounds are there.
Thanks again,
WP.
-
On 22/01/2014 at 22:42, xxxxxxxx wrote:
With integer types, as long as there is no overflow in value, they will always be 100% accurate.
Floating point (real) numbers are represented using an IEEE numerical formatting of bits. Since you can only have so many decimal places represented (so-called 'significant digits'), it is mathematically impossible to avoid rounding errors and, in subtraction, cancellation errors. All one can do is use algorithms and methodologies that minimize direct and accumulative errors. No amount of architecture can avoid these potential numerical discrepancies.
-
On 23/01/2014 at 05:15, xxxxxxxx wrote:
The kind of thing where 60 != 60.0!!
Yes for floating point number you need to assume this all the time.> I'm a little surprised that modern day architecture can't handle these things, but never-the-less workarounds are there.
This has nothing to do with the architecture but more with the mathematics.
Some real numbers need infinite memory so it is just impossible to represent them as floatin point numbers.
Of course there are some other ways to represent them that have other advantages and disadvantages, but there is just no hardware support for them.Some times one need to use much slower way for calculation such as "arbitrary-precision-arithmetic".
Remo