Reputation: 93
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
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