/
Amount.cs
298 lines (262 loc) · 10.8 KB
/
Amount.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
using Qowaiv.Conversion.Financial;
namespace Qowaiv.Financial;
/// <summary>Represents an amount.</summary>
[DebuggerDisplay("{DebuggerDisplay}")]
[Serializable]
[SingleValueObject(SingleValueStaticOptions.Continuous, typeof(decimal))]
[OpenApiDataType(description: "Decimal representation of a currency amount.", example: 15.95, type: "number", format: "amount")]
[TypeConverter(typeof(AmountTypeConverter))]
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(Json.Financial.AmountJsonConverter))]
#endif
public readonly partial struct Amount : IXmlSerializable, IFormattable, IEquatable<Amount>, IComparable, IComparable<Amount>
#if NET8_0_OR_GREATER
, IMinMaxValue<Amount>
#endif
{
/// <summary>Represents the smallest possible value of the amount.</summary>
public static Amount MinValue => new(decimal.MinValue);
/// <summary>Represents the biggest possible value of the amount.</summary>
public static Amount MaxValue => new(decimal.MaxValue);
/// <summary>Gets the sign of the value of the amount.</summary>
[Pure]
public int Sign() => m_Value.Sign();
/// <summary>Returns the absolute value of the amount.</summary>
[Pure]
public Amount Abs() => new(m_Value.Abs());
/// <summary>Rounds the amount value to zero decimal places.</summary>
[Pure]
public Amount Round() => Round(0);
/// <summary>Rounds the amount value to a specified number of decimal places.</summary>
/// <param name="decimals">
/// A value from -28 to 28 that specifies the number of decimal places to round to.
/// </param>
/// <remarks>
/// A negative value for <paramref name="decimals"/> lowers precision to tenfold, hundredfold, and bigger.
/// </remarks>
[Pure]
public Amount Round(int decimals) => Round(decimals, DecimalRounding.BankersRound);
/// <summary>Rounds the amount value to a specified number of decimal places.</summary>
/// <param name="decimals">
/// A value from -28 to 28 that specifies the number of decimal places to round to.
/// </param>
/// <param name="mode">
/// The mode of rounding applied.
/// </param>
/// <remarks>
/// A negative value for <paramref name="decimals"/> lowers precision to tenfold, hundredfold, and bigger.
/// </remarks>
[Pure]
public Amount Round(int decimals, DecimalRounding mode) => (Amount)m_Value.Round(decimals, mode);
/// <summary>Rounds the amount value to the closed number that is a multiple of the specified factor.</summary>
/// <param name="multipleOf">
/// The factor of which the number should be multiple of.
/// </param>
[Pure]
public Amount RoundToMultiple(decimal multipleOf) => RoundToMultiple(multipleOf, DecimalRounding.BankersRound);
/// <summary>Rounds the amount value to the closed number that is a multiple of the specified factor.</summary>
/// <param name="multipleOf">
/// The factor of which the number should be multiple of.
/// </param>
/// <param name="mode">
/// The rounding method used to determine the closed by number.
/// </param>
[Pure]
public Amount RoundToMultiple(decimal multipleOf, DecimalRounding mode) => (Amount)m_Value.RoundToMultiple(multipleOf, mode);
/// <summary>Returns the larger of two amounts.</summary>
/// <param name="val1">
/// The second of the two amounts to compare.
/// </param>
/// <param name="val2">
/// The first of the two amounts to compare.
/// </param>
/// <returns>
/// Parameter val1 or val2, whichever is larger.
/// </returns>
[Pure]
public static Amount Max(Amount val1, Amount val2) => val1 > val2 ? val1 : val2;
/// <summary>Returns the largest of the amounts.</summary>
/// <param name="values">
/// The amounts to compare.
/// </param>
/// <returns>
/// The amount with the largest value.
/// </returns>
[Pure]
public static Amount Max(params Amount[] values) => Guard.NotNull(values).Max();
/// <summary>Returns the smaller of two amounts.</summary>
/// <param name="val1">
/// The second of the two amounts to compare.
/// </param>
/// <param name="val2">
/// The first of the two amounts to compare.
/// </param>
/// <returns>
/// Parameter val1 or val2, whichever is smaller.
/// </returns>
[Pure]
public static Amount Min(Amount val1, Amount val2) => val1 < val2 ? val1 : val2;
/// <summary>Returns the smallest of the amounts.</summary>
/// <param name="values">
/// The amounts to compare.
/// </param>
/// <returns>
/// The amount with the smallest value.
/// </returns>
[Pure]
public static Amount Min(params Amount[] values) => Guard.NotNull(values).Min();
/// <summary>Serializes the amount to a JSON node.</summary>
/// <returns>
/// The serialized JSON number.
/// </returns>
/// <remarks>
/// Some <see cref="decimal.Zero"/> representations will be cast to a
/// <see cref="double"/> value that slightly smaller than 0, at least
/// enough to have a <see cref="string"/> representation of -0.
/// </remarks>
[Pure]
public decimal ToJson() => m_Value;
/// <summary>Returns a <see cref="string"/> that represents the current Amount for debug purposes.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => this.DebuggerDisplay("¤{0:0.00########}");
/// <inheritdoc />
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
=> StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted)
? formatted
: m_Value.ToString(format, Money.GetNumberFormatInfo(formatProvider));
/// <summary>Gets an XML string representation of the amount.</summary>
[Pure]
private string ToXmlString() => ToString(CultureInfo.InvariantCulture);
/// <summary>Deserializes the amount from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized amount.
/// </returns>
[Pure]
public static Amount FromJson(decimal json) => new(json);
/// <summary>Deserializes the amount from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized amount.
/// </returns>
[Pure]
public static Amount FromJson(double json) => new(Cast.ToDecimal<Amount>(json));
/// <summary>Deserializes the amountfrom a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized amount.
/// </returns>
[Pure]
public static Amount FromJson(long json) => new(json);
/// <summary>Casts a decimal to an amount.</summary>
public static explicit operator Amount(decimal val) => new(val);
/// <summary>Casts a decimal to an amount.</summary>
public static explicit operator Amount(double val) => Create(val);
/// <summary>Casts a long to an amount.</summary>
public static explicit operator Amount(long val) => new(val);
/// <summary>Casts a int to an amount.</summary>
public static explicit operator Amount(int val) => new(val);
/// <summary>Casts an Amount to a decimal.</summary>
public static explicit operator decimal(Amount val) => val.m_Value;
/// <summary>Casts an Amount to a double.</summary>
public static explicit operator double(Amount val) => (double)val.m_Value;
/// <summary>Casts an Amount to a long.</summary>
public static explicit operator long(Amount val) => (long)val.m_Value;
/// <summary>Casts an Amount to an int.</summary>
public static explicit operator int(Amount val) => (int)val.m_Value;
/// <summary>Converts the string to an amount.
/// A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="s">
/// A string containing an Amount to convert.
/// </param>
/// <param name="provider">
/// The specified format provider.
/// </param>
/// <param name="result">
/// The result of the parsing.
/// </param>
/// <returns>
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, IFormatProvider? provider, out Amount result)
=> TryParse(s, NumberStyles.Currency, provider, out result);
/// <summary>Converts the string to an amount.
/// A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="s">
/// A string containing an Amount to convert.
/// </param>
/// <param name="style">
/// The preferred number style.
/// </param>
/// <param name="provider">
/// The specified format provider.
/// </param>
/// <param name="result">
/// The result of the parsing.
/// </param>
/// <returns>
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
Guard(style);
return style.HasFlag(NumberStyles.AllowCurrencySymbol)
? ParseMoney(s, provider, out result)
: ParseAmount(s, style, provider, out result);
static bool ParseMoney(string? s, IFormatProvider? provider, out Amount result)
{
if (Money.TryParse(s, provider, out Money money))
{
result = money.Amount;
return true;
}
else
{
result = default;
return false;
}
}
static bool ParseAmount(string? s, NumberStyles style, IFormatProvider? provider, out Amount result)
{
if (decimal.TryParse(s, style, provider, out decimal amount))
{
result = new(amount);
return true;
}
else
{
result = default;
return false;
}
}
static void Guard(NumberStyles style)
{
var extra = style & ~NumberStyles.Currency;
if (extra != NumberStyles.None)
{
throw new ArgumentOutOfRangeException(nameof(style), string.Format(QowaivMessages.ArgumentOutOfRange_NumberStyleNotSupported, extra));
}
}
}
/// <summary>Creates an Amount from a Decimal.</summary >
/// <param name="val" >
/// A decimal describing an Amount.
/// </param >
[Pure]
public static Amount Create(decimal val) => new(val);
/// <summary>Creates an Amount from a Double.</summary >
/// <param name="val" >
/// A decimal describing an Amount.
/// </param >
[Pure]
public static Amount Create(double val) => Create(Cast.ToDecimal<Amount>(val));
}