Test procedure
We know that there is a rounding error in the floating point operation. In some special cases, the rounding error can be accumulated to a very large extent. Let's take a look at the test program:
1 using System; 2 3 static class DecimalSumTester 4 { 5 static void Main(string[] args) 6 { 7 try 8 { 9 var n = (args.Length > 0) ? int.Parse(args[0]) : 10;10 for (var i = 0; i < 3; i++) Console.WriteLine(F(n, i));11 }12 catch (Exception ex) { Console.WriteLine(ex.Message); }13 }14 15 static decimal F(int n, int k)16 {17 var z = 0.1m + k * 1000000000000000000000000000m;18 var w = decimal.Round(z) / 2;19 while (n-- > 0) z += z / 2 - w;20 return z - w * 2;21 }22 }
In this program:
- The 19th rows are accumulated through the while loop: Z + = z/2-W ;. W remains unchanged, while Z increases through accumulation.
- The value of N is used to determine the number of while loops.
- The first parameter n of the F method in rows 15th to 21 is the number of cycles. The second parameter k determines the size of the integer part that accumulates the initial value.
- In this program, the parameter k is only 0, 1, and 2. If the arithmetic operation has no error, the return value of the F method should be the same in the three cases.
Compile and run in Linux
Compile and run the 64-bit arch Linux mono 3.0.4 environment:
work$ dmcs DecimalSumTester.cswork$ mono DecimalSumTester.exe 241683.41121960282325744628906252730.92730.9
The first line in the above results is the calculated exact value. The value of the second and third rows should theoretically be equal to that of the first row. However, due to the cumulative result of the rounding error of the floating point operation, the final answer error is quite large. As mentioned in my previous article "talking about the system. Decimal structure", the Rounding Rule of decimal arithmetic operations in Linux environment is rounding, which may result in a large error. In Windows, the Rounding Rule is four homes, six in five directions. So let's take a look.
Compile and run in Windows
Compile and run Windows 7 SP1 32-bit in Microsoft. NET Framework 4.5:
D: \ work>CSC decimalsumtester. CSMicrosoft (r) Visual C # compiler version 4.0.30319.17929 is used for Microsoft (R). Net Framework 4.5 copyright ownership (c) Microsoft Corporation. All rights reserved. D: \ work>Decimalsumtester 241683.41121960282325744628906252097.90.1
The running result is very different from that in Linux. However, the error is quite large.
Debug program
Let's take a look at what happened during the operation. Insert some debugging statements in the above test program:
1 using System; 2 3 static class DecimalSumDebug 4 { 5 static void Main(string[] args) 6 { 7 var n = (args.Length > 0) ? int.Parse(args[0]) : 10; 8 for (var i = 0; i < 3; i++) 9 Console.WriteLine(F(n, i).ToString().PadRight(77, '-'));10 }11 12 static decimal F(int n, int k)13 {14 var z = 0.1m + k * 1000000000000000000000000000m;15 var w = decimal.Round(z) / 2;16 for (decimal x, y; n-- > 0; z += x)17 {18 x = (y = z / 2) - w;19 Console.WriteLine("{0,-30}: {1,-30}: {2}", z, y, x);20 }21 return z - w * 2;22 }23 }
The function of this program is the same as that of the previous test program. It is only a statement that adds the value of the intermediate variable in the calculation process.
Debugging in Linux
Compile and run in the mono environment of arch Linux, and output a lot of debugging information:
work$ dmcs DecimalSumDebug.cswork$ mono DecimalSumDebug.exe 100.1 : 0.05 : 0.050.15 : 0.075 : 0.0750.225 : 0.1125 : 0.11250.3375 : 0.16875 : 0.168750.50625 : 0.253125 : 0.2531250.759375 : 0.3796875 : 0.37968751.1390625 : 0.56953125 : 0.569531251.70859375 : 0.854296875 : 0.8542968752.562890625 : 1.2814453125 : 1.28144531253.8443359375 : 1.92216796875 : 1.922167968755.76650390625----------------------------------------------------------------1000000000000000000000000000.1: 500000000000000000000000000.05: 0.051000000000000000000000000000.2: 500000000000000000000000000.1 : 0.11000000000000000000000000000.3: 500000000000000000000000000.15: 0.151000000000000000000000000000.5: 500000000000000000000000000.25: 0.251000000000000000000000000000.8: 500000000000000000000000000.4 : 0.41000000000000000000000000001.2: 500000000000000000000000000.6 : 0.61000000000000000000000000001.8: 500000000000000000000000000.9 : 0.91000000000000000000000000002.7: 500000000000000000000000001.35: 1.351000000000000000000000000004.1: 500000000000000000000000002.05: 2.051000000000000000000000000006.2: 500000000000000000000000003.1 : 3.19.3--------------------------------------------------------------------------2000000000000000000000000000.1: 1000000000000000000000000000.1: 0.12000000000000000000000000000.2: 1000000000000000000000000000.1: 0.12000000000000000000000000000.3: 1000000000000000000000000000.2: 0.22000000000000000000000000000.5: 1000000000000000000000000000.3: 0.32000000000000000000000000000.8: 1000000000000000000000000000.4: 0.42000000000000000000000000001.2: 1000000000000000000000000000.6: 0.62000000000000000000000000001.8: 1000000000000000000000000000.9: 0.92000000000000000000000000002.7: 1000000000000000000000000001.4: 1.42000000000000000000000000004.1: 1000000000000000000000000002.1: 2.12000000000000000000000000006.2: 1000000000000000000000000003.1: 3.19.3--------------------------------------------------------------------------
In the above results, the first group is the exact value without any rounding error. The rounding of the last two groups is different, but the final result is the same. For how to perform such rounding, refer to my previous article.
Debugging in Windows
Compiling and running in windows. NET Framework also outputs a lot of debugging information:
D:\work> DecimalSumDebug 100.1 : 0.05 : 0.050.15 : 0.075 : 0.0750.225 : 0.1125 : 0.11250.3375 : 0.16875 : 0.168750.50625 : 0.253125 : 0.2531250.759375 : 0.3796875 : 0.37968751.1390625 : 0.56953125 : 0.569531251.70859375 : 0.854296875 : 0.8542968752.562890625 : 1.2814453125 : 1.28144531253.8443359375 : 1.92216796875 : 1.922167968755.76650390625----------------------------------------------------------------1000000000000000000000000000.1: 500000000000000000000000000.05: 0.051000000000000000000000000000.2: 500000000000000000000000000.1 : 0.11000000000000000000000000000.3: 500000000000000000000000000.15: 0.151000000000000000000000000000.4: 500000000000000000000000000.2 : 0.21000000000000000000000000000.6: 500000000000000000000000000.3 : 0.31000000000000000000000000000.9: 500000000000000000000000000.45: 0.451000000000000000000000000001.4: 500000000000000000000000000.7 : 0.71000000000000000000000000002.1: 500000000000000000000000001.05: 1.051000000000000000000000000003.2: 500000000000000000000000001.6 : 1.61000000000000000000000000004.8: 500000000000000000000000002.4 : 2.47.2--------------------------------------------------------------------------2000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 02000000000000000000000000000.1: 1000000000000000000000000000 : 00.1--------------------------------------------------------------------------
Similarly, the result of the first group is an accurate value, and the last two groups have different rounding errors. The rounding errors in the second group are better than those in Linux. The third group is very bad, and the accumulated value is directly rounded to 0.
System. Double Data Type
A slight modification to the previous test program can be used to test the rounding error of the double data type:
1 using System; 2 3 static class DoubleSumTester 4 { 5 static void Main(string[] args) 6 { 7 try 8 { 9 var e = (args.Length > 0) ? int.Parse(args[0]) : 15;10 for (var i = 0; i < 3; i++) Console.WriteLine(F(10, i, e));11 }12 catch (Exception ex) { Console.WriteLine(ex.Message); }13 }14 15 static double F(int n, int k, int e)16 {17 var z = 0.1 + k * Math.Pow(10, e);18 var w = (long)z / 2.0;19 while (n-- > 0) z += z / 2 - w;20 return z - w * 2;21 }22 }
At this time, the number of cycles is fixed to 10. The command line parameter specifies the 10 power index of the integer that affects the rounding error. The running result is as follows:
work$ dmcs DoubleSumTester.cswork$ mono DoubleSumTester.exe 05.766503906255.766503906255.76650390625002work$ mono DoubleSumTester.exe 15.766503906255.766503906249935.76650390625work$ mono DoubleSumTester.exe 45.766503906255.766503906317675.76650390624854work$ mono DoubleSumTester.exe 105.766503906255.766574859619145.76650238037109work$ mono DoubleSumTester.exe 125.766503906255.7619628906255.7666015625work$ mono DoubleSumTester.exe 145.766503906255.68755.0625work$ mono DoubleSumTester.exe 155.7665039062590work$ mono DoubleSumTester.exe 165.7665039062500
This time, the running results in Linux and Windows are the same. Similarly, the first line of each operation is the exact value, and the other two rows are the result of different rounding errors.
References
- Blog: system. Decimal Structure