System.Math.Round bug?
I was writing a function for rounding a number to two places. And I found a bug when I was trying to round specific values. So, I ran the code:
class Program {
static void Main(string[] args) {
int limit = 100;
for (int number = 0; number <= limit; number++) {
Console.WriteLine((System.Math.Round((double)(number+0.995),2,MidpointRounding.AwayFromZero)));
}
}
}
And I found that: 8.99 9.99 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 32.99 33.99 34.99 35.99 36.99 37.99 38.99 39.99
numbers are not rounded to their next value.
When I run the same code till 1500: I get the numbers:
8.99 9.99 32.99 33.99 34.99 35.99 36.99 37.99 38.99 39.99 1024.99 1025.99 1026.99 1027.99 1028.99 1029.99 1030.99 1031.99 1032.99 1033.99 1034.99 1035.99 1036.99 1037.99 1038.99 1039.99 1040.99 1041.99 1042.99 1043.99 1044.99 1045.99 1046.99 1047.99 1048.99 1049.99 1050.99 1051.99 1052.99 1053.99 1054.99 1055.99 1056.99 1057.99 1058.99 1059.99 1060.99 1061.99 1062.99 1063.99 106开发者_Python百科4.99 1065.99 1066.99 1067.99 1068.99 1069.99 1070.99 1071.99 1072.99 1073.99 1074.99 1075.99 1076.99 1077.99 1078.99 1079.99 1080.99 1081.99 1082.99 1083.99 1084.99 1085.99 1086.99 1087.99 1088.99 1089.99 1090.99 1091.99 1092.99 1093.99 1094.99 1095.99 1096.99 1097.99 1098.99 1099.99 1100.99 1101.99 1102.99 1103.99 1104.99 1105.99 1106.99 1107.99 1108.99 1109.99 1110.99 1111.99 1112.99 1113.99 1114.99 1115.99 1116.99 1117.99 1118.99 1119.99 1120.99 1121.99 1122.99 1123.99 1124.99 1125.99 1126.99 1127.99 1128.99 1129.99 1130.99 1131.99 1132.99 1133.99 1134.99 1135.99 1136.99 1137.99 1138.99 1139.99 1140.99 1141.99 1142.99 1143.99 1144.99 1145.99 1146.99 1147.99 1148.99 1149.99 1150.99 1151.99 1152.99 1153.99 1154.99 1155.99 1156.99 1157.99 1158.99 1159.99 1160.99 1161.99 1162.99 1163.99 1164.99 1165.99 1166.99 1167.99 1168.99 1169.99 1170.99 1171.99 1172.99 1173.99 1174.99 1175.99 1176.99 1177.99 1178.99 1179.99 1180.99 1181.99 1182.99 1183.99 1184.99 1185.99 1186.99 1187.99 1188.99 1189.99 1190.99 1191.99 1192.99 1193.99 1194.99 1195.99 1196.99 1197.99 1198.99 1199.99 1200.99 1201.99 1202.99 1203.99 1204.99 1205.99 1206.99 1207.99 1208.99 1209.99 1210.99 1211.99 1212.99 1213.99 1214.99 1215.99 1216.99 1217.99 1218.99 1219.99 1220.99 1221.99 1222.99 1223.99 1224.99 1225.99 1226.99 1227.99 1228.99 1229.99 1230.99 1231.99 1232.99 1233.99 1234.99 1235.99 1236.99 1237.99 1238.99 1239.99 1240.99 1241.99 1242.99 1243.99 1244.99 1245.99 1246.99 1247.99 1248.99 1249.99 1250.99 1251.99 1252.99 1253.99 1254.99 1255.99 1256.99 1257.99 1258.99 1259.99 1260.99 1261.99 1262.99 1263.99 1264.99 1265.99 1266.99 1267.99 1268.99 1269.99 1270.99 1271.99 1272.99 1273.99 1274.99 1275.99 1276.99 1277.99 1278.99 1279.99 1280.99 1281.99 1282.99 1283.99 1284.99 1285.99 1286.99 1287.99 1288.99 1289.99 1290.99 1291.99 1292.99 1293.99 1294.99 1295.99 1296.99 1297.99 1298.99 1299.99 1300.99 1301.99 1302.99 1303.99 1304.99 1305.99 1306.99 1307.99 1308.99 1309.99
which are not rounded to next number! Has anyone any idea about why its happening for these specific numbers!
The problem is that when you're taking the value of "number + 0.995", that's not going to be exactly number + 0.995 for the normal reasons of binary floating point not representing decimal values precisely.
Sometims it will be a little over, and sometimes it'll be a little under. When it's under, and you round that result to two decimal places, you end up with the "0.99" bit. It's possible that Math.Round
tries to take this into account to some extent, but I'm not sure of the exact details.
The solution - where possible - is to use decimal
instead of double
for things where the decimal digits matter.
See my articles on binary floating point and decimal floating point in .NET for more information.
Here's a program using my DoubleConverter code which shows the inaccuracy after adding 0.995:
using System;
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 100; i++)
{
double d = i + 0.995;
Console.WriteLine(DoubleConverter.ToExactString(d));
}
}
}
Results:
0.99499999999999999555910790149937383830547332763671875 1.99500000000000010658141036401502788066864013671875 2.99500000000000010658141036401502788066864013671875 3.99500000000000010658141036401502788066864013671875 4.99500000000000010658141036401502788066864013671875 5.99500000000000010658141036401502788066864013671875 6.99500000000000010658141036401502788066864013671875 7.99500000000000010658141036401502788066864013671875 8.9949999999999992184029906638897955417633056640625 9.9949999999999992184029906638897955417633056640625 10.9949999999999992184029906638897955417633056640625 11.9949999999999992184029906638897955417633056640625 12.9949999999999992184029906638897955417633056640625 13.9949999999999992184029906638897955417633056640625 14.9949999999999992184029906638897955417633056640625 15.9949999999999992184029906638897955417633056640625 16.995000000000000994759830064140260219573974609375 17.995000000000000994759830064140260219573974609375 18.995000000000000994759830064140260219573974609375 19.995000000000000994759830064140260219573974609375 20.995000000000000994759830064140260219573974609375 ...
(Note how although it starts going 0.994 at 8, it continues as far as 15 - whereas Math.Round
"corrects" itself at 10.)
This is not a matter of a proper handling. Try to explain this:
Console.WriteLine(Math.Round(4.995, 2, MidpointRounding.AwayFromZero)); // 5.00
Console.WriteLine(Math.Round(5.995, 2, MidpointRounding.AwayFromZero)); // 6.00
Console.WriteLine(Math.Round(9.995, 2, MidpointRounding.AwayFromZero)); // 9.99
As you can easily see, 1% is far greater than the double precision limit. And there are NO arithmetic operations which can be blamed for precision loss.
Math.Round and double.ToString() has different rounding behavior. Math.Round just doesn't works some times. I had to drop Math.Round and had to develop my own rounding to solve that.
精彩评论