0

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?

Emanuele
  • 73
  • 3
  • 11
  • Well, i would say it all depends on where the original source value for that decimal variable came from and what precisely it looked like. This should normally be unrelated to culture settings. (See here for an example that illustrates `$"My decimal is {d}"` outputting both with and without trailing zeros, entirely depending on the source value for the decimal: https://dotnetfiddle.net/VHqHej) –  Oct 13 '22 at 10:29
  • The source of the data is not always the same, in the microservice. Somethimes it is read from a database column, somethimes it is a property of a json that has been received. On the AzureFunction instead the source is always a property of a json that is being elaborated. – Emanuele Oct 13 '22 at 10:55

1 Answers1

2

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.

BurnsBA
  • 4,347
  • 27
  • 39
  • Ok, thank you. Basically it is not possible to define a general behaviour for my problem, is it right? – Emanuele Oct 13 '22 at 14:50