Decimal Values and Trailing Zeroes

The System.Decimal data type differs from other floating-point data types in the way that it represents the number of digits after the decimal point. With decimals, the precision of the number is retained, even if the trailing digits are not significant.

System.Decimal

The System.Decimal data type, which has the alias of decimal in C#, is used to store floating-point numbers with a high level of precision. Decimals do not suffer from rounding issues to the same extent as single or double-precision floating-point values, although rounding errors are still possible. This makes them ideal for some scientific and monetary calculations, where rounding errors must be minimised.

To achieve the additional precision, Decimals use a greater number of bits to represent a number. A single-precision floating-point number requires thirty-two bits and a double-precision value uses sixty-four. The Decimal structure uses hundred and twenty-eight bits, although not all of these actually hold data. One bit determines the sign of the number; whether it is positive or negative. Ninety-six bits store an integer that, along with the sign bit, can range from -79,228,162,514,264,337,593,543,950,335 to 79,228,162,514,264,337,593,543,950,335. Finally, some of the remaining bits set a scaling value that determines the position of the decimal point.

A Decimal's scaling value is a power of ten between 100 and 1028. The integer part of the Decimal is divided by this scaling factor to determine the actual value held in the structure. For example, if the 96-bit integer gives the value 987,654,321,012,345,678,901 and the scaling factor is 1015, the value of the Decimal is 987,654.321012345678901.

Trailing Zeroes

Due to the manner in which Decimals are stored, it's quite possible for a value to include zeroes that are not significant digits. Depending upon the way in which you initialise or calculate values, the scaling factor may be large enough that such trailing digits are recognised. The extra digits are important for some calculations, as they may indicate the precision of a calculation. For example, a value of 1.1 might suggest the result of a calculation rounded to one decimal place, with the true figure being between 1.05 and 1.15. A value of 1.1000 would indicate greater accuracy, and a true result of between 1.0995 and 1.1005.

Additional zeroes have no bearing on calculations, except for specifying the accuracy of the result. However, they can be seen when displaying values. You might see them if you call the ToString method or when using data binding with technologies such as Windows Presentation Foundation (WPF).

To demonstrate, run the following code in the Mainmethod of a console application. Although both of the Decimals in this sample effectively hold the same value, the output shows that the number of decimal places used when assigning the value is retained.

The scaling factor is also affected by multiplication and division operations. Multiplication can increase the number of recorded decimal places, whilst division can reduce the scaling value. The following code shows the number of decimal places increasing due to multiplication.

You should consider the precision of Decimals when you need to convert them to strings for display purposes. If you do not, your users may see values that include trailing zeroes, which might be inappropriate.

If you always wish to display values with a specific number of decimal places, you can apply standard format specifiers when converting to a string. The code below demonstrates this by formatting a decimal value with two decimal places.

decimal d = 1.2500M;
Console.WriteLine(d.ToString("F2")); // 1.25

In some cases you might not wish to show your values with a fixed number of decimal digits. You may just want to remove the trailing zeroes and keep all of the significant digits. A standard format specifier is not suitable for this purpose. Instead, you can use a custom picture format.

The code below shows such a format string. Here the hash characters (#) specify that digits should only be included if they are significant to the value. There are twenty-eight hashes, ensuring that trailing zeroes for all scaling factors can be removed, whilst retaining up to twenty-eight non-zero fractional digits.

In some cases you might wish to change the scaling factor of a decimal value in order to remove the trailing zeroes. This would change the underlying value, rather than applying formatting when converting to a string. As mentioned earlier, division can reduce the scaling factor. If you divide a Decimal by one, where the value of one includes twenty-eight trailing zeroes, any trailing zeroes are removed. Any significant decimal digits remain.