FYI: "Case study" of some numerical challenges (and the solution [not])
2016-08-11, 23:36 (This post was last modified: 2016-08-12, 12:09 by Nils Schmidt.)
2016-08-11, 23:36 (This post was last modified: 2016-08-12, 12:09 by Nils Schmidt.)
Thank you for your assistance! :)
I would like to be a little bit more technical with the following example.
Spoiler alert: I also got the solution while I was writing this text, so I possibly need no more help.
No, ... there is more work to do...
To be honest, I found a way to access the sourcecode from PrimGen2 without asking Mike for help ;)
Yeah... I was really curious how PrimGen2 works under hood...
So... lets get started with the ring primitives (I hope Mike will not hold this against me :D ):
Here is the hacked code from PrimGen2 (C#, CLR)
The function Math.Round(number, 4) rounds by default to even numbers (known as Banker's Rounding).
In this case, four digits are preserved.
Java uses another default rounding method. So I had to write .NET's Math.round() by myself. I called it round4f.
There should be no precision loss (due to the use of the arbitrary BigDecimal class).
Well, as I coder, I might be a bit lazy sometimes... :)
I just copy & paste the C# code to Java and slightly modified it (I had to use my custom round function)...
And here is the code from LDPartEditor (Java, JVM)
However, someone must be very carful here, how to do the rounding for the output on the screen.
I build this off-wall ring with both tools.
The header is identical on LDPE and PrimGen2
...but when we take a look at the data from LDPE:
...we can spot a 0.2617, and PrimGen outputs...
...suddenly 0.2618 appears! This technically not equal to 0.2617. With some effort, you'll see that 0.2617 is in reality a 0.26175. It should be rounded to 0.2618...
First, I used the DecimalFormat class to archive the classical "half up" rounding with an output of four decimal places.
...strangely, it did not round with "half up"... even after setting the rounding mode accordingly...
so I had to write my own interpretation of a DecimalFormatter...
and now it works :)
[size=large]It doesn't.[/size]
I got a 0.5036499999999999.
LDPE says it's 0.5036.
PrimGen2 says it's 0.5037.
Arrgh!
I would like to be a little bit more technical with the following example.
Spoiler alert: I also got the solution while I was writing this text, so I possibly need no more help.
No, ... there is more work to do...
To be honest, I found a way to access the sourcecode from PrimGen2 without asking Mike for help ;)
Yeah... I was really curious how PrimGen2 works under hood...
So... lets get started with the ring primitives (I hope Mike will not hold this against me :D ):
Here is the hacked code from PrimGen2 (C#, CLR)
Code:
for (int i = 0; i < Segments; i++) {
objdat.LinePoint1.X = Math.Round(Math.Cos(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * InnerDiameter;
objdat.LinePoint1.Y = 0.0;
objdat.LinePoint1.Z = Math.Round(Math.Sin(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * InnerDiameter;
objdat.LinePoint2.X = Math.Round(Math.Cos((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * InnerDiameter;
objdat.LinePoint2.Y = 0.0;
objdat.LinePoint2.Z = Math.Round(Math.Sin((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * InnerDiameter;
objdat.LinePoint3.X = Math.Round(Math.Cos((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * (Width + InnerDiameter);
objdat.LinePoint3.Y = 0.0;
objdat.LinePoint3.Z = Math.Round(Math.Sin((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * (Width + InnerDiameter);
objdat.LinePoint4.X = Math.Round(Math.Cos(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * (Width + InnerDiameter);
objdat.LinePoint4.Y = 0.0;
objdat.LinePoint4.Z = Math.Round(Math.Sin(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0), 4) * (Width + InnerDiameter);
// Generate output/objects here...
}
The function Math.Round(number, 4) rounds by default to even numbers (known as Banker's Rounding).
In this case, four digits are preserved.
Java uses another default rounding method. So I had to write .NET's Math.round() by myself. I called it round4f.
Code:
private double round4f(double d) {
return new BigDecimal(d).setScale(4, RoundingMode.HALF_EVEN).doubleValue();
}
There should be no precision loss (due to the use of the arbitrary BigDecimal class).
Well, as I coder, I might be a bit lazy sometimes... :)
I just copy & paste the C# code to Java and slightly modified it (I had to use my custom round function)...
And here is the code from LDPartEditor (Java, JVM)
Code:
for (int i = 0; i < Segments; i++) {
double objdatLinePoint1X = round4f(Math.cos(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * InnerDiameter;
double objdatLinePoint1Y = 0.0;
double objdatLinePoint1Z = round4f(Math.sin(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * InnerDiameter;
double objdatLinePoint2X = round4f(Math.cos((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * InnerDiameter;
double objdatLinePoint2Y = 0.0;
double objdatLinePoint2Z = round4f(Math.sin((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * InnerDiameter;
double objdatLinePoint3X = round4f(Math.cos((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * (Width + InnerDiameter);
double objdatLinePoint3Y = 0.0;
double objdatLinePoint3Z = round4f(Math.sin((((i + 1) * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * (Width + InnerDiameter);
double objdatLinePoint4X = round4f(Math.cos(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * (Width + InnerDiameter);
double objdatLinePoint4Y = 0.0;
double objdatLinePoint4Z = round4f(Math.sin(((i * (360.0 / ((double) Divisions))) * 3.1415926535897931) / 180.0)) * (Width + InnerDiameter);
// Generate output/objects here...
}
However, someone must be very carful here, how to do the rounding for the output on the screen.
I build this off-wall ring with both tools.
The header is identical on LDPE and PrimGen2
Code:
0 Ring 7.5 x 0.0056 Width 2.5
0 Name: 360\1-180ring7.5w2.5.dat
0 Author: Nils Schmidt [BlackBrick89]
0 !LDRAW_ORG Unofficial_Primitive
0 !LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt
0 BFC CERTIFY CCW
...but when we take a look at the data from LDPE:
Code:
4 16 10 0 0 9.998 0 0.175 7.4985 0 0.1313 7.5 0 0
4 16 9.998 0 0.175 9.994 0 0.349 7.4955 0 0.2617 7.4985 0 0.1313
0 // Build by LDPartEditor (PrimGen 2.X)
...we can spot a 0.2617, and PrimGen outputs...
Code:
4 16 10 0 0 9.998 0 0.175 7.4985 0 0.1313 7.5 0 0
4 16 9.998 0 0.175 9.994 0 0.349 7.4955 0 0.2618 7.4985 0 0.1313
0 // Build by Primitive Generator 2
...suddenly 0.2618 appears! This technically not equal to 0.2617. With some effort, you'll see that 0.2617 is in reality a 0.26175. It should be rounded to 0.2618...
First, I used the DecimalFormat class to archive the classical "half up" rounding with an output of four decimal places.
...strangely, it did not round with "half up"... even after setting the rounding mode accordingly...
so I had to write my own interpretation of a DecimalFormatter...
and now it works :)
[size=large]It doesn't.[/size]
I got a 0.5036499999999999.
LDPE says it's 0.5036.
PrimGen2 says it's 0.5037.
Arrgh!