I want to convert a decimal a with scale > 0 to its equivalent decimal b with scale 0 (suppose that there is an equivalent decimal without losing precision). Success is defined by having b.ToString()
return a string without any trailing zeroes or by extracting the scale via GetBits and confirming that it is 0.
Easy options I found:
Decimal scale2 = new Decimal(100, 0, 0, false, 2);
string scale2AsString = scale2.ToString(System.Globalization.CultureInfo.InvariantCulture);
// toString includes trailing zeroes
Assert.IsTrue(scale2AsString.Equals("1.00"));
// can use format specifier to specify format
string scale2Formatted = scale2.ToString("G0");
Assert.IsTrue(scale2Formatted.Equals("1"));
// but what if we want to pass the decimal to third party code that does not use format specifiers?
// option 1, use Decimal.Truncate or Math.Truncate (Math.Truncate calls Decimal.Truncate, I believe)
Decimal truncated = Decimal.Truncate(scale2);
string truncatedAsString = truncated.ToString(System.Globalization.CultureInfo.InvariantCulture);
Assert.IsTrue(truncatedAsString.Equals("1"));
// option 2, division trick
Decimal divided = scale2 / 1.000000000000000000000000000000000m;
string dividedAsString = divided.ToString(System.Globalization.CultureInfo.InvariantCulture);
Assert.IsTrue(dividedAsString.Equals("1"));
// option 3, if we expect the decimal to fit in int64, convert to int64 and back
Int64 asInt64 = Decimal.ToInt64(scale2);
Decimal backToDecimal = new Decimal(asInt64);
string backToDecimalString = backToDecimal.ToString(System.Globalization.CultureInfo.InvariantCulture);
Assert.IsTrue(backToDecimalString.Equals("1"));
// option 4, convert to BigInteger then back using BigInteger's explicit conversion to decimal
BigInteger convertedToBigInteger = new BigInteger(scale2);
Decimal bigIntegerBackToDecimal = (Decimal)convertedToBigInteger;
string bigIntegerBackToDecimalString = bigIntegerBackToDecimal.ToString(System.Globalization.CultureInfo.InvariantCulture);
Assert.IsTrue(bigIntegerBackToDecimalString.Equals("1"));
So plenty of options, and certainly there are more. But which of these options are actually guaranteed to work?
Option 1: MSDN does not mention that the scale is changed when calling Truncate
, so using this method seems to be relying on an implementation detail. Internally, Truncate calls FCallTruncate, for which I did not find any documentation.
Option 2 may be mandated by the CLI spec, but I did not find which exact specification that would be, I did not find it in the ECMA specification.
Option 3 (ToInt64 also uses FCallTruncate internally) will work judging by the reference source (the constructor taking an ulong sets flags and this scale to 0) but the documentation again makes no mention of scale.
Option 4, BigInteger calls Decimal.Truncate, with the comment:
// First truncate to get scale to 0 and extract bits
int[] bits = Decimal.GetBits(Decimal.Truncate(value));
So clearly Microsoft internally also thinks that Decimal.Truncate will set the scale to 0.
But I am looking for a method that is guaranteed to work without relying on implementation details and works for all the decimal where this can technically work (cannot rescale a Decimal.MaxValue for example). None of the options above seems to fit the bill for this requirement.