Page 2 of 3
If someone can explain it to me?
Posted: Sat Mar 23, 2019 5:56 pm
by softdevo@tiscali.it
For those who have not read the image correctly, here is the simplification of the code under discussion:
(As I said, the same code in another application gives the right result: 870)
LOCAL cOre AS STRING
LOCAL nNUORE AS System.Double
LOCAL nTOMIC AS System.Double
cOre := SELF:Gior:Text // "8,70"
nNUORE := Val(cOre) // 8.70
nTOMIC := (System.Double)(nNUORE*100,0) // 869.99999999999989 ERROR!!!!!!!!!!!!!!
Danilo
If someone can explain it to me?
Posted: Sat Mar 23, 2019 5:59 pm
by softdevo@tiscali.it
excuse me
LOCAL cOre AS STRING
LOCAL nNUORE AS System.Double
LOCAL nTOMIC AS System.Double
cOre := SELF:Gior:Text // "8,70"
nNUORE := Val(cOre) // 8.70
nTOMIC := (System.Double)(nNUORE*100) // 869.99999999999989
If someone can explain it to me?
Posted: Sat Mar 23, 2019 6:03 pm
by wriedmann
Ciao Danilo,
if you put that value to a database field with 2 decimals, or display it using a picture, it is rounded to a correct value.
I have these issues in VO all the time, and therefore I'm using Round() a lot, other than setting SetFloatDelta() to 0.0001.
In .NET fortunately we have the "decimals" datatype, and AFAIK it is the datatype the X# runtime uses internally for decimal numbers.
Wolfgang
If someone can explain it to me?
Posted: Sat Mar 23, 2019 6:09 pm
by softdevo@tiscali.it
Thanks Wolfgang, since the number is always a whole I solved this
nTOMIC := (System.Double)Round(nNUORE*100,0) //(System.Double)(nNUORE*100) //
But the problem remains …
Danilo
If someone can explain it to me?
Posted: Sat Mar 23, 2019 6:17 pm
by wriedmann
Hi Danilo,
the problem is not a problem of VO or X#, but a problem in the Microsoft Floating Point Library, or better in the representation of float or double. The float or real8 datatype was never planned for exact calculations, unfortunately.
Use System.Decimal whenever possible.
Wolfgang
If someone can explain it to me?
Posted: Sat Mar 23, 2019 6:31 pm
by Chris
Danilo, please try this code in VO, or any other language:
Code: Select all
FUNCTION Start() AS INT
LOCAL f1 AS REAL8
LOCAL f2 AS REAL8
SetFloatDelta(0)
f1 := 0.015
f1 := f1 + f1 + f1
f1 *= 10.0
f2 := 0.45
? f1==f2 , f1<f2 , f1>f2
WAIT
RETURN 0
Similarly in c#:
Code: Select all
public class Program
{
static void Main()
{
double f1;
double f2;
f1 = 0.015;
f1 = f1 + f1 + f1;
f1 *= 10.0;
f2 = 0.45;
System.Console.WriteLine(f1==f2);
System.Console.WriteLine(f1<f2);
System.Console.WriteLine(f1>f2);
}
}
In both samples, you would expect f1 == f2 == 0.45, but both samples return FALSE for == and TRUE for <. This is because none of those values can be represented in binary accurately, there is some precision loss, and the more arithmetic you do with decimal numbers, the more precision is lost.
When you store 0.015 in the f1 var, the actual value stored is something like 0.0149999999791 or similar, because there is no way to represent 0.015 in binary notation that the REAL8, REAL4, FLOAT etc types use. Yes, when you see the value in the debugger or print it with a function, you will usually see 0.015, but this is because there is a runtime function used to print the value, and this is smart, knowing about the precision lost and converts 0.0149999999791 to 0.015 before displaying it. But after a number of calculations with float numbers, you end up with accumulated precision lost in each calculation, so the end result is slightly wrong, and this is why you see this strange value in the debugger sometimes and sometimes not.
For this reason, there is the Decimal type in .Net, which does not store values in binary format internally, but uses a decimal representation. This makes it much slower, but also has zero precision lost, 0.015 will always be 0.015 with this type.
If someone can explain it to me?
Posted: Sat Mar 23, 2019 6:40 pm
by Chris
Hi Wolfgang,
wriedmann wrote:
In .NET fortunately we have the "decimals" datatype, and AFAIK it is the datatype the X# runtime uses internally for decimal numbers.
X# is working exactly as in VO:
REAL8 and FLOAT use 8 bytes for representing the stored value, in binary format. REAL8 is a synonym for the .Net type System.Double, FLOAT is a special class which again uses a System.Double and also includes formatting info as in VO.
REAL4 uses 4 bytes, in binary format again, it is a synonym for System.Single.
When you want to use the Decimal type, you need to specify it explicitly.
If someone can explain it to me?
Posted: Sun Mar 24, 2019 8:10 am
by softdevo@tiscali.it
Ok guys, thanks to everyone, everything is clear.
Now I ask if I use System.Decimal instead of System.Double in my calculation class for attendance management that makes thousands of operations on decimal numbers, will the slowness of the decimal be appreciable or irrelevant?
Danilo
If someone can explain it to me?
Posted: Sun Mar 24, 2019 9:56 am
by lumberjack
Danilo,
softdevo@tiscali.it wrote:Ok guys, thanks to everyone, everything is clear.
Now I ask if I use System.Decimal instead of System.Double in my calculation class for attendance management that makes thousands of operations on decimal numbers, will the slowness of the decimal be appreciable or irrelevant?
It all depends, will try to explain with a bad coding example:
Code: Select all
LOCAL x, y as <SomeFloatType>
x := 1.2 * (2.0 / 3.0) <rest of calculation>
WHILE ctr < 1000000 .AND. x <> 0.0
IF (1.2 * (2.0 / 3.0) <rest of calculation>) < 345.34555
y += 1.2 * (2.0 / 3.0) <rest of calc>
x := 1.2 * (2.0 / 3.0) <rest of change x positively>
ELSE
y -= 1.2 / (2.0 / 3.0) <whatever>
x := 1.2 / (2.0 / 3.0) <rest of change x negatively>
ENDIF
ctr++
ENDDO
Think we get the idea, we heavily making use of floating point calculations and we end up with (2.0 / 3.0) that cannot be represented exactly, we cannot get past that, but we can minimize the impact:
Code: Select all
LOCAL x, y as <SomeFloatType>
LOCAL xneg, xpos AS <FloatType>
xneg := 1.2 / (2.0 / 3.0)
xpos := 1.2 * (2.0 / 3.0)
x := xpos <rest of calculation>
WHILE ctr < 1000000 .AND. x <> 0.0
IF (x <rest of calculation>) < 345.34555
y += xpos <rest of calc>
x := xpos <rest of change x positively>
ELSE
y -= xneg <whatever>
x := xneg <rest of change x negatively>
ENDIF
ctr++
ENDDO
So yes it will have an impact, but you can minimize that impact that reduce the number of floating point arithmetic calculations. This is an extreme example but the impact will be reduced aka. speed up the process.
In a single non-looped actual implementation it should not distract the user experience. Try where possible to eliminate floating point bottlenecks.
Hope that helps,
If someone can explain it to me?
Posted: Sun Mar 24, 2019 11:47 am
by Chris
softdevo@tiscali.it wrote:Ok guys, thanks to everyone, everything is clear.
Now I ask if I use System.Decimal instead of System.Double in my calculation class for attendance management that makes thousands of operations on decimal numbers, will the slowness of the decimal be appreciable or irrelevant?
Danilo
Most probably the speed difference will be irrelevant, because usually in our apps it's other things that slow down performance (disk access, GUI updates etc) and not numeric calculations. Of course I would not use the Decimal type if I wanted to calculate fractals, but for regular calculations, let's say 1,000 or 10,000 numeric calculations per command the difference will be zero. But of course it's always better if you give it a try and see in practice what difference it makes, if any.