FastDecimal is a fast, fixed-point decimal type with strongly typed, configurable precision for .NET.
FastDecimal uses generics to configure the precision, so the precision is part of the type. If you for example want a 64-bit decimal with six fractional digits you will declare it as
FastDecimal64<Six>. This allows the JIT compiler to output custom assembly code tailored to this precision.
FastDecimal comes in a 32-bit and a 64-bit variant, FastDecimal32<T> and FastDecimal64<T>. FastDecimal has the same range as the underlying integer, eg. -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 for FastDecimal64 and the generic parameter decides where the decimal point is, so for example FastDecimal64<Four> will have four fractional digits and a range from -922,337,203,685,477.5808 to 922,337,203,685,477.5807.
The simplest way to initialize a FastDecimal is to construct it from a decimal:
FastDecimal64<Four> price = new (23.54m);
FastDecimal implements IParsable and ISpanParsable, so you can create a FastDecimal from a string or directly in a deserializer:
var price = FastDecimal64<Four>.Parse("23.54", NumberFormatInfo.InvariantInfo);
For doing calculations, FastDecimal implements INumber, so most operations that you would need for a numeric type will work:
var price = new FastDecimal64<Four>(23.54m);
var quantity = new FastDecimal64<Four>(5m);
var commission = new FastDecimal64<Four>(0.20m);
var totalCost = price * quantity + commission;
FastDecimal implements both checked and unchecked operators. By default operators are unchecked, but if you want arithmetic operations to throw an OverflowException, when the result would overflow, then you can use a checked statement:
var value = checked(FastDecimal64<Four>.MaxValue++) // will throw OverflowException
This behavior is different from decimal which will always throw an OverflowException when an operation results in an overflow.
When doing multiplication and division the result might have more fractional digits, than the input numbers, e.g. 2.5 * 0.1 = 0.25. If the result of a calculation cannot be represented exactly then FastDecimal will by default round to the nearest number and if the number is halfway between two numbers it will round to the nearest even number:
var a = new FastDecimal64<One>(2.5m);
var b = new FastDecimal64<One>(0.1m);
var c = a * b; // c will be 0.2 because it is the nearest even number
This is known as banker's rounding and is also the default behavior for decimal and IEEE 754 floating point numbers such as float and double.
If you want to use a different rounding mode FastDecimal provides methods where you can provide the rounding mode as a parameter:
var a = new FastDecimal64<One>(2.5m);
var b = new FastDecimal64<One>(0.1m);
var c = FastDecimal64<One>.Multiply(a, b, MidpointRounding.AwayFromZero); // c will be 0.3 because it rounds away from zero
The main reason to use FastDecimal over the built-in decimal type is for performance since decimal strictly can represent more values than FastDecimal64. But if you don't need the range offered by decimal, then FastDecimal can be a lot faster!
The arithmetic operations in FastDecimal are written to be highly performant, and in general, FastDecimal should be faster than decimal. To get an idea of the performance difference between FastDecimal and decimal you can take a look at the benchmarks.
FastDecimal64 and FastDecimal32 are approximately equally fast (on 64-bit systems) when doing the same calculations, but FastDecimal32 takes up less memory, so it might be a good choice if you are allocating a lot of objects with decimal values, e.g. if you are getting thousands of price updates per second.
And casting from FastDecimal32 to FastDecimal64 is extremely fast, so you can easily use FastDecimal32 for storage and FastDecimal64 for doing calculations.
| decimal | FastDecimal64 | FastDecimal32 | |
|---|---|---|---|
| Integer range | -79,228,162,514,264,337,593,543,950,335 - 79,228,162,514,264,337,593,543,950,335 | -9,223,372,036,854,775,808 - 9,223,372,036,854,775,807 | -2,147,483,648 - 2,147,483,647 |
| Scaling factor | 0 - 28 | 0 - 19 | 0 - 9 |
| Size | 128 bit | 64 bit | 32 bit |