Decimal хранит точность в анализируемой строке в С#? Каковы последствия?

Во время разговора в IRC кто-то указал на следующее:

decimal.Parse("1.0000").ToString() // 1.0000
decimal.Parse("1.00").ToString() // 1.00

Как/почему тип decimal сохраняет точность (или, скорее, значимые цифры) как это? У меня создалось впечатление, что эти два значения равны, а не разные.

Это также вызывает дополнительные вопросы:

  • Как определяется количество значимых цифр во время математических операций?
  • Сохраняется ли количество значимых цифр во время сериализации?
  • Влияет ли текущая культура на то, как это обрабатывается?

Ответы

Ответ 1

Как определяется количество значимых цифр во время математических операций?

Это указано в ECMA-334 спецификации С# 4 11.1.7 с .112

Десятичное число представлено как целое число, масштабируемое степенью десяти. Для десятичные числа с абсолютным значением менее 1,0 м, это значение точно для по крайней мере, на 28-м знаке после запятой. Для десятичных знаков с абсолютным значением больше или равно 1,0 м, значение точно не менее 28 цифры.

Сохраняется ли количество значимых цифр во время сериализации?

Да, с сериализацией значение и его точность не меняются

[Serializable]
public class Foo
{
    public decimal Value;
}

class Program
{
    static void Main(string[] args)
    {
        decimal d1 = decimal.Parse("1.0000");
        decimal d2 = decimal.Parse("1.00");

        Debug.Assert(d1 ==d2);

        var foo1 = new Foo() {Value = d1};
        var foo2 = new Foo() {Value = d2};

        IFormatter formatter = new BinaryFormatter();
        Stream stream = new FileStream("data.bin", FileMode.Create, FileAccess.Write, FileShare.None);
        formatter.Serialize(stream, d1);
        stream.Close();

        formatter = new BinaryFormatter();
        stream = new FileStream("data.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
        decimal deserializedD1 = (decimal)formatter.Deserialize(stream);
        stream.Close();

        Debug.Assert(d1 == deserializedD1);

        Console.WriteLine(d1); //1.0000
        Console.WriteLine(d2); //1.00
        Console.WriteLine(deserializedD1); //1.0000

        Console.Read();
    }
}

Влияет ли текущая культура на то, как это обрабатывается?

Текущая культура влияет только на то, как десятичная строка может быть проанализирована из строки, например, она может обрабатывать '.' или ',' как десятичную символа точки или символа валюты, если вы его предоставите, например "£ 123.4500". Культура не изменяет способ хранения объекта внутри, и это не влияет на его точность.

Внутри decimal имеет мантисс, экспоненту и знак, поэтому нет места ни для чего другого.

Ответ 2

A decimal состоит из 96-битного целого числа и коэффициента масштабирования (количество цифр после десятичной точки), которое варьируется от 0 до 28. Таким образом:

  • 1.000 становится 1000 с коэффициентом масштабирования 3.

Ответ 3

В дополнение к сообщению, которое я вижу здесь, я лично добавлю примечание:

всегда во время манипуляций с сохранением с использованием чисел с плавающей запятой/десятичной/двойной числами учитывайте culture, в котором вы находитесь, или вы собираетесь сохранить. Код, как здесь, написан сначала, но окончательный переход к полной беспорядочной и некультурной независимой архитектуре.

Используйте Decimal.Parse(String, IFormatProvider).

По-моему, методы (Parse From/To), которые не имеют параметра culture, должны быть удалены из библиотеки, чтобы заставить разработчика задуматься об этом очень важном аспекте.