Emanuele
Emanuele

Reputation: 93

NET 6 decimal default interpolation conversion

I have a NET6 microservice that interpolate decimal variables truncating the decimal portion when the decimal part is 0, so assuming that d is my decimal variable with value 5.0:

$"My decimal is {d}"

result converted as "My decimal is 5" The same instruction in a AzureFunction v4 with NET6 result converted as "My decimal is 5.0".

In both of their Startup classes I have set the default culture in the Configure as follows:

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

What I would expect is to have the same result. Am I missing something?

Upvotes: 0

Views: 375

Answers (1)

BurnsBA
BurnsBA

Reputation: 4939

The decimal structure contains an exponent, sign bit, and "value". The "value" is across three int internally, the other information is in a "flags" int. This is not so different from IEEE float, except decimal uses base 10 exponents.

Relevant source code

// The lo, mid, hi, and flags fields contain the representation of the
// Decimal value. The lo, mid, and hi fields contain the 96-bit integer
// part of the Decimal. Bits 0-15 (the lower word) of the flags field are
// unused and must be zero; bits 16-23 contain must contain a value between
// 0 and 28, indicating the power of 10 to divide the 96-bit integer part
// by to produce the Decimal value; bits 24-30 are unused and must be zero;
// and finally bit 31 indicates the sign of the Decimal value, 0 meaning
// positive and 1 meaning negative.
//
// NOTE: Do not change the order in which these fields are declared. The
// native methods in this class rely on this particular order.
private int flags;
private int hi;
private int lo;
private int mid;

So for example, as MySkullCaveIsADarkPlace says, if you have a decimal d_5 = decimal.Parse("5"), and a d_5_00 = decimal.Parse("5.00"), these will have different internal structure.

For the curious, you can retrieve these values using reflection

using System.Reflection;
FieldInfo[] fields = typeof(decimal).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

decimal d_5 = decimal.Parse("5");
decimal d_5_00 = decimal.Parse("5.00");
int flags;
int exponent;

flags = (int)fields[0].GetValue(d_5);
exponent = (0xff0000 & flags) >> 16;
Console.WriteLine("5:");
Console.WriteLine($"flags: {flags} (exponent={exponent}), lo: {fields[2].GetValue(d_5)}, mid: {fields[3].GetValue(d_5)}");

flags = (int)fields[0].GetValue(d_5_00);
exponent = (0xff0000 & flags) >> 16;
Console.WriteLine("5.00:");
Console.WriteLine($"flags: {flags} (exponent={exponent}), lo: {fields[2].GetValue(d_5_00)}, mid: {fields[3].GetValue(d_5_00)}");

result:

5:
flags: 0 (exponent=0), lo: 5, mid: 0
5.00:
flags: 131072 (exponent=2), lo: 500, mid: 0

If you want a specific number of decimal places, then call with a string format (e.g., $"{d_5:N2}" -> 5.00), or use Decimal.Truncate for no decimal places. Otherwise the default ToString will use the available information (flags, hi, mid, lo) in the object.

Upvotes: 2

Related Questions