tiny-sharp-rb 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,664 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "money/bank/variable_exchange"
4
+ require "money/bank/single_currency"
5
+ require "money/money/arithmetic"
6
+ require "money/money/constructors"
7
+ require "money/money/formatter"
8
+ require "money/money/allocation"
9
+ require "money/money/locale_backend"
10
+
11
+ # "Money is any object or record that is generally accepted as payment for
12
+ # goods and services and repayment of debts in a given socio-economic context
13
+ # or country." -Wikipedia
14
+ #
15
+ # An instance of Money represents an amount of a specific currency.
16
+ #
17
+ # Money is a value object and should be treated as immutable.
18
+ #
19
+ # @see https://en.wikipedia.org/wiki/Money
20
+ class Money
21
+ include Comparable
22
+ include Money::Arithmetic
23
+ extend Constructors
24
+
25
+ # Raised when smallest denomination of a currency is not defined
26
+ class UndefinedSmallestDenomination < StandardError; end
27
+
28
+ # Convenience method for fractional part of the amount. Synonym of #fractional
29
+ #
30
+ # @return [Integer] when infinite_precision is false
31
+ # @return [BigDecimal] when infinite_precision is true
32
+ #
33
+ # @see infinite_precision
34
+ def cents
35
+ fractional
36
+ end
37
+
38
+ # The value of the monetary amount represented in the fractional or subunit
39
+ # of the currency.
40
+ #
41
+ # For example, in the US dollar currency the fractional unit is cents, and
42
+ # there are 100 cents in one US dollar. So given the Money representation of
43
+ # one US dollar, the fractional interpretation is 100.
44
+ #
45
+ # Another example is that of the Kuwaiti dinar. In this case the fractional
46
+ # unit is the fils and there 1000 fils to one Kuwaiti dinar. So given the
47
+ # Money representation of one Kuwaiti dinar, the fractional interpretation is
48
+ # 1000.
49
+ #
50
+ # @return [Integer] when infinite_precision is false
51
+ # @return [BigDecimal] when infinite_precision is true
52
+ #
53
+ # @see infinite_precision
54
+ def fractional
55
+ # Ensure we have a BigDecimal. If the Money object is created
56
+ # from YAML, @fractional can end up being set to a Float.
57
+ fractional = as_d(@fractional)
58
+
59
+ return_value(fractional)
60
+ end
61
+
62
+ # Round a given amount of money to the nearest possible amount in cash value. For
63
+ # example, in Swiss franc (CHF), the smallest possible amount of cash value is
64
+ # CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and CHF 0.08 to
65
+ # CHF 0.10.
66
+ #
67
+ # @return [Integer] when infinite_precision is false
68
+ # @return [BigDecimal] when infinite_precision is true
69
+ #
70
+ # @see infinite_precision
71
+ def round_to_nearest_cash_value
72
+ warn "[DEPRECATION] `round_to_nearest_cash_value` is deprecated - use " \
73
+ "`to_nearest_cash_value.fractional` instead"
74
+
75
+ to_nearest_cash_value.fractional
76
+ end
77
+
78
+ # Round a given amount of money to the nearest possible money in cash value.
79
+ # For example, in Swiss franc (CHF), the smallest possible amount of cash
80
+ # value is CHF 0.05. Therefore, this method rounds CHF 0.07 to CHF 0.05, and
81
+ # CHF 0.08 to CHF 0.10.
82
+ #
83
+ # @return [Money]
84
+ def to_nearest_cash_value
85
+ unless self.currency.smallest_denomination
86
+ raise UndefinedSmallestDenomination,
87
+ "Smallest denomination of this currency is not defined"
88
+ end
89
+
90
+ fractional = as_d(@fractional)
91
+ smallest_denomination = as_d(self.currency.smallest_denomination)
92
+ rounded_value =
93
+ (fractional / smallest_denomination)
94
+ .round(0, self.class.rounding_mode) * smallest_denomination
95
+
96
+ dup_with(fractional: return_value(rounded_value))
97
+ end
98
+
99
+ # @!attribute [r] currency
100
+ # @return [Currency] The money's currency.
101
+ # @!attribute [r] bank
102
+ # @return [Money::Bank::Base] The +Money::Bank+-based object which currency
103
+ # exchanges are performed with.
104
+
105
+ attr_reader :currency, :bank
106
+
107
+ # Class Methods
108
+ class << self
109
+
110
+ # @!attribute [rw] default_bank
111
+ # Used to set a default bank for currency exchange.
112
+ #
113
+ # Each Money object is associated with a bank
114
+ # object, which is responsible for currency exchange. This property
115
+ # allows you to specify the default bank object. The default value for
116
+ # this property is an instance of +Bank::VariableExchange.+ It allows
117
+ # one to specify custom exchange rates.
118
+ #
119
+ # @return [Money::Bank::Base]
120
+ #
121
+ # @!attribute default_formatting_rules
122
+ # Used to define a default hash of rules for every time
123
+ # +Money#format+ is called. Rules provided on method call will be
124
+ # merged with the default ones. To overwrite a rule, just provide the
125
+ # intended value while calling +format+.
126
+ #
127
+ # @see Money::Formatter#initialize Money::Formatter for more details
128
+ #
129
+ # @example
130
+ # Money.default_formatting_rules = { display_free: true }
131
+ # Money.new(0, "USD").format # => "free"
132
+ # Money.new(0, "USD").format(display_free: false) # => "$0.00"
133
+ #
134
+ # @return [Hash]
135
+ #
136
+ # @!attribute [rw] default_infinite_precision
137
+ # @return [Boolean] Use this to enable infinite precision cents as the
138
+ # global default
139
+ #
140
+ # @!attribute [rw] conversion_precision
141
+ # Used to specify precision for converting Rational to BigDecimal
142
+ #
143
+ # @return [Integer]
144
+ #
145
+ # @!attribute [rw] strict_eql_compare
146
+ # Use this to specify how +Money#eql?+ behaves. Opt-in to the new
147
+ # behavior by setting this to +true+ and disable warnings when comparing
148
+ # zero amounts with different currencies.
149
+ #
150
+ # @example
151
+ # Money.strict_eql_compare = false # (default)
152
+ # Money.new(0, "USD").eql?(Money.new(0, "EUR")) # => true
153
+ # # => [DEPRECATION] warning
154
+ #
155
+ # Money.strict_eql_compare = true
156
+ # Money.new(0, "USD").eql?(Money.new(0, "EUR")) # => false
157
+ #
158
+ # @return [Boolean]
159
+ #
160
+ # @see Money#eql
161
+ attr_accessor :default_formatting_rules,
162
+ :default_infinite_precision,
163
+ :conversion_precision,
164
+ :strict_eql_compare
165
+ attr_reader :locale_backend
166
+ attr_writer :default_bank
167
+ end
168
+
169
+ # @!attribute default_currency
170
+ # @return [Money::Currency] The default currency, which is used when
171
+ # +Money.new+ is called without an explicit currency argument.
172
+ def self.default_currency
173
+ if @default_currency.nil?
174
+ nil
175
+ elsif @default_currency.respond_to?(:call)
176
+ Money::Currency.new(@default_currency.call)
177
+ else
178
+ Money::Currency.new(@default_currency)
179
+ end
180
+ end
181
+
182
+ def self.default_currency=(currency)
183
+ @default_currency = currency
184
+ end
185
+
186
+ # Modified to support thread-local bank override
187
+ def self.default_bank
188
+ # Check for thread-local bank first, then fall back to global default
189
+ return Thread.current[:money_bank] if Thread.current[:money_bank]
190
+
191
+ if @default_bank.respond_to?(:call)
192
+ @default_bank.call
193
+ else
194
+ @default_bank
195
+ end
196
+ end
197
+
198
+ # Thread-safe bank switching method
199
+ # Temporarily changes the default bank in the current thread only
200
+ #
201
+ # @param [Money::Bank::Base] bank The bank to use within the block
202
+ # @yield The block within which the bank will be changed
203
+ # @return [Object] block results
204
+ #
205
+ # @example
206
+ # Money.with_bank(european_bank) do
207
+ # Money.new(100, "USD").exchange_to("EUR")
208
+ # end
209
+ def self.with_bank(bank)
210
+ original_bank = Thread.current[:money_bank]
211
+ Thread.current[:money_bank] = bank
212
+ yield
213
+ ensure
214
+ Thread.current[:money_bank] = original_bank
215
+ end
216
+
217
+ def self.locale_backend=(value)
218
+ @locale_backend = value ? LocaleBackend.find(value) : nil
219
+ end
220
+
221
+ # @attr_writer rounding_mode Use this to specify the rounding mode
222
+ def self.rounding_mode=(new_rounding_mode)
223
+ @rounding_mode = new_rounding_mode
224
+ end
225
+
226
+ def self.setup_defaults
227
+ # Set the default bank for creating new +Money+ objects.
228
+ self.default_bank = Bank::VariableExchange.instance
229
+
230
+ # Default to using currency backend
231
+ self.locale_backend = :currency
232
+
233
+ # Default to not using infinite precision cents
234
+ self.default_infinite_precision = false
235
+
236
+ # Default rounding mode toward the nearest neighbor; if the neighbors are equidistant, round away from zero
237
+ self.rounding_mode = BigDecimal::ROUND_HALF_UP
238
+
239
+ # Default the conversion of Rationals precision to 16
240
+ self.conversion_precision = 16
241
+
242
+ # Defaults to the deprecated behavior where
243
+ # `Money.new(0, "USD").eql?(Money.new(0, "EUR"))` is true.
244
+ self.strict_eql_compare = false
245
+ end
246
+
247
+ def self.inherited(base)
248
+ base.setup_defaults
249
+ end
250
+
251
+ setup_defaults
252
+
253
+ # Use this to return the rounding mode.
254
+ #
255
+ # @return [BigDecimal::ROUND_MODE] rounding mode
256
+ def self.rounding_mode
257
+ return Thread.current[:money_rounding_mode] if Thread.current[:money_rounding_mode]
258
+
259
+ @rounding_mode
260
+ end
261
+
262
+ # Temporarily changes the rounding mode in a given block.
263
+ #
264
+ # @param [BigDecimal::ROUND_MODE] mode
265
+ #
266
+ # @yield The block within which rounding mode will be changed. Its return
267
+ # value will also be the return value of the whole method.
268
+ #
269
+ # @return [Object] block results
270
+ #
271
+ # @example
272
+ # fee = Money.with_rounding_mode(BigDecimal::ROUND_HALF_DOWN) do
273
+ # Money.new(1200) * BigDecimal('0.029')
274
+ # end
275
+ def self.with_rounding_mode(mode)
276
+ original_mode = Thread.current[:money_rounding_mode]
277
+ Thread.current[:money_rounding_mode] = mode
278
+ yield
279
+ ensure
280
+ Thread.current[:money_rounding_mode] = original_mode
281
+ end
282
+
283
+ # Adds a new exchange rate to the default bank and return the rate.
284
+ #
285
+ # @param [Currency, String, Symbol] from_currency Currency to exchange from.
286
+ # @param [Currency, String, Symbol] to_currency Currency to exchange to.
287
+ # @param [Numeric] rate Rate to exchange with.
288
+ #
289
+ # @return [Numeric]
290
+ #
291
+ # @example
292
+ # Money.add_rate("USD", "CAD", 1.25) #=> 1.25
293
+ def self.add_rate(from_currency, to_currency, rate)
294
+ Money.default_bank.add_rate(from_currency, to_currency, rate)
295
+ end
296
+
297
+ # Sets the default bank to be a SingleCurrency bank that raises on
298
+ # currency exchange. Useful when apps operate in a single currency at a time.
299
+ def self.disallow_currency_conversion!
300
+ self.default_bank = Bank::SingleCurrency.instance
301
+ end
302
+
303
+ # Creates a new Money object of value given in the +unit+ of the given
304
+ # +currency+.
305
+ #
306
+ # @param [Numeric] amount The numerical value of the money.
307
+ # @param [Currency, String, Symbol] currency The currency format.
308
+ # @param [Hash] options Optional settings for the new Money instance
309
+ # @option [Money::Bank::*] :bank The exchange bank to use.
310
+ #
311
+ # @example
312
+ # Money.from_amount(23.45, "USD") # => #<Money fractional:2345 currency:USD>
313
+ # Money.from_amount(23.45, "JPY") # => #<Money fractional:23 currency:JPY>
314
+ #
315
+ # @return [Money]
316
+ #
317
+ # @see #initialize
318
+ def self.from_amount(amount, currency = default_currency, options = {})
319
+ raise ArgumentError, "'amount' must be numeric" unless Numeric === amount
320
+
321
+ currency = Currency.wrap(currency) || Money.default_currency
322
+ raise Currency::NoCurrency, 'must provide a currency' if currency.nil?
323
+
324
+ value = amount.to_d * currency.subunit_to_unit
325
+ new(value, currency, options)
326
+ end
327
+
328
+ # DEPRECATED.
329
+ #
330
+ # @see Money.from_amount
331
+ def self.from_dollars(amount, currency = default_currency, options = {})
332
+ warn "[DEPRECATION] `Money.from_dollars` is deprecated in favor of " \
333
+ "`Money.from_amount`."
334
+
335
+ from_amount(amount, currency, options)
336
+ end
337
+
338
+ class << self
339
+ alias_method :from_cents, :new
340
+ end
341
+
342
+ # Creates a new Money object of value given in the
343
+ # +fractional unit+ of the given +currency+.
344
+ #
345
+ # Alternatively you can use the convenience
346
+ # methods like {Money.ca_dollar} and {Money.us_dollar}.
347
+ #
348
+ # @param [Object] obj Either the fractional value of the money,
349
+ # a Money object, or a currency. (If passed a currency as the first
350
+ # argument, a Money will be created in that currency with fractional value
351
+ # = 0.
352
+ # @param [Currency, String, Symbol] currency The currency format.
353
+ # @param [Hash] options Optional settings for the new Money instance
354
+ # @option [Money::Bank::*] :bank The exchange bank to use.
355
+ #
356
+ # @return [Money]
357
+ #
358
+ # @example
359
+ # Money.new(100) #=> #<Money @fractional=100 @currency="USD">
360
+ # Money.new(100, "USD") #=> #<Money @fractional=100 @currency="USD">
361
+ # Money.new(100, "EUR") #=> #<Money @fractional=100 @currency="EUR">
362
+ #
363
+ def initialize(obj, currency = nil, options = {})
364
+ # For backwards compatibility, if options is not a Hash, treat it as a bank parameter
365
+ unless options.is_a?(Hash)
366
+ options = { bank: options }
367
+ end
368
+
369
+ @fractional = as_d(obj.respond_to?(:fractional) ? obj.fractional : obj)
370
+ @currency = obj.respond_to?(:currency) ? obj.currency : Currency.wrap(currency)
371
+ @currency ||= Money.default_currency
372
+ @bank = obj.respond_to?(:bank) ? obj.bank : options[:bank]
373
+ @bank ||= Money.default_bank
374
+
375
+ # BigDecimal can be Infinity and NaN, money of that amount does not make sense
376
+ raise ArgumentError, 'must be initialized with a finite value' unless @fractional.finite?
377
+ raise Currency::NoCurrency, 'must provide a currency' if @currency.nil?
378
+ end
379
+
380
+ # DEPRECATED.
381
+ #
382
+ # @see #amount
383
+ def dollars
384
+ warn "[DEPRECATION] `Money#dollars` is deprecated in favor of " \
385
+ "`Money#amount`."
386
+
387
+ amount
388
+ end
389
+
390
+ # Returns the numerical value of the money.
391
+ #
392
+ # @return [BigDecimal]
393
+ #
394
+ # @example
395
+ # Money.new(1_00, "USD").amount # => BigDecimal("1.00")
396
+ #
397
+ # @see #to_d
398
+ # @see #fractional
399
+ def amount
400
+ to_d
401
+ end
402
+
403
+ # Returns a Integer hash value based on the +fractional+ and +currency+ attributes
404
+ # in order to use functions like & (intersection), group_by, etc.
405
+ #
406
+ # @return [Integer]
407
+ #
408
+ # @example
409
+ # Money.new(100).hash #=> 908351
410
+ def hash
411
+ [fractional.hash, currency.hash].hash
412
+ end
413
+
414
+ # Uses +Currency#symbol+. If +nil+ is returned, defaults to "¤".
415
+ #
416
+ # @return [String]
417
+ #
418
+ # @example
419
+ # Money.new(100, "USD").symbol #=> "$"
420
+ def symbol
421
+ currency.symbol || "¤"
422
+ end
423
+
424
+ # Common inspect function
425
+ #
426
+ # @return [String]
427
+ def inspect
428
+ "#<#{self.class.name} fractional:#{fractional} currency:#{currency}>"
429
+ end
430
+
431
+ # Returns the amount of money as a string.
432
+ #
433
+ # @return [String]
434
+ #
435
+ # @example
436
+ # Money.ca_dollar(100).to_s #=> "1.00"
437
+ def to_s
438
+ format thousands_separator: '',
439
+ no_cents_if_whole: currency.decimal_places == 0,
440
+ symbol: false,
441
+ ignore_defaults: true
442
+ end
443
+
444
+ # Return the amount of money as a BigDecimal.
445
+ #
446
+ # @return [BigDecimal]
447
+ #
448
+ # @example
449
+ # Money.us_dollar(1_00).to_d #=> BigDecimal("1.00")
450
+ def to_d
451
+ as_d(fractional) / as_d(currency.subunit_to_unit)
452
+ end
453
+
454
+ # Return the amount of money as a Integer.
455
+ #
456
+ # @return [Integer]
457
+ #
458
+ # @example
459
+ # Money.us_dollar(1_00).to_i #=> 1
460
+ def to_i
461
+ to_d.to_i
462
+ end
463
+
464
+ # Return the amount of money as a float. Floating points cannot guarantee
465
+ # precision. Therefore, this function should only be used when you no longer
466
+ # need to represent currency or working with another system that requires
467
+ # floats.
468
+ #
469
+ # @return [Float]
470
+ #
471
+ # @example
472
+ # Money.us_dollar(100).to_f #=> 1.0
473
+ def to_f
474
+ to_d.to_f
475
+ end
476
+
477
+ # Returns a new Money instance in a given currency leaving the amount intact
478
+ # and not performing currency conversion.
479
+ #
480
+ # @param [Currency, String, Symbol] new_currency Currency of the new object.
481
+ #
482
+ # @return [self]
483
+ def with_currency(new_currency)
484
+ new_currency = Currency.wrap(new_currency)
485
+ if !new_currency || currency == new_currency
486
+ self
487
+ else
488
+ dup_with(currency: new_currency)
489
+ end
490
+ end
491
+
492
+ # Conversion to +self+.
493
+ #
494
+ # @return [self]
495
+ def to_money(given_currency = nil)
496
+ given_currency = Currency.wrap(given_currency)
497
+ if given_currency.nil? || self.currency == given_currency
498
+ self
499
+ else
500
+ exchange_to(given_currency)
501
+ end
502
+ end
503
+
504
+ # Receive the amount of this money object in another Currency.
505
+ #
506
+ # @param [Currency, String, Symbol] other_currency Currency to exchange to.
507
+ #
508
+ # @yield [n] Optional block to use when rounding after exchanging one currency
509
+ # for another.
510
+ # @yieldparam [Float] n The resulting float after exchanging one currency for
511
+ # another.
512
+ # @yieldreturn [Integer]
513
+ #
514
+ # @return [Money]
515
+ #
516
+ # @example
517
+ # Money.new(2000, "USD").exchange_to("EUR")
518
+ # Money.new(2000, "USD").exchange_to("EUR") {|x| x.round}
519
+ # Money.new(2000, "USD").exchange_to(Currency.new("EUR"))
520
+ def exchange_to(other_currency, &rounding_method)
521
+ other_currency = Currency.wrap(other_currency)
522
+ if self.currency == other_currency
523
+ self
524
+ else
525
+ @bank.exchange_with(self, other_currency, &rounding_method)
526
+ end
527
+ end
528
+
529
+ # Receive a money object with the same amount as the current Money object
530
+ # in United States dollar.
531
+ #
532
+ # @return [Money]
533
+ #
534
+ # @example
535
+ # n = Money.new(100, "CAD").as_us_dollar
536
+ # n.currency #=> #<Money::Currency id: usd>
537
+ def as_us_dollar
538
+ exchange_to("USD")
539
+ end
540
+
541
+ # Receive a money object with the same amount as the current Money object
542
+ # in Canadian dollar.
543
+ #
544
+ # @return [Money]
545
+ #
546
+ # @example
547
+ # n = Money.new(100, "USD").as_ca_dollar
548
+ # n.currency #=> #<Money::Currency id: cad>
549
+ def as_ca_dollar
550
+ exchange_to("CAD")
551
+ end
552
+
553
+ # Receive a money object with the same amount as the current Money object
554
+ # in euro.
555
+ #
556
+ # @return [Money]
557
+ #
558
+ # @example
559
+ # n = Money.new(100, "USD").as_euro
560
+ # n.currency #=> #<Money::Currency id: eur>
561
+ def as_euro
562
+ exchange_to("EUR")
563
+ end
564
+
565
+ # Splits a given amount in parts without losing pennies. The left-over pennies will be
566
+ # distributed round-robin amongst the parties. This means that parts listed first will likely
567
+ # receive more pennies than ones listed later.
568
+ #
569
+ # Pass [2, 1, 1] as input to give twice as much to part1 as part2 or
570
+ # part3 which results in 50% of the cash to party1, 25% to part2, and 25% to part3. Passing a
571
+ # number instead of an array will split the amount evenly (without losing pennies when rounding).
572
+ #
573
+ # @param [Array<Numeric>, Numeric] parts how amount should be distributed to parts
574
+ #
575
+ # @return [Array<Money>]
576
+ #
577
+ # @example
578
+ # Money.new(5, "USD").allocate([3, 7]) #=> [Money.new(2), Money.new(3)]
579
+ # Money.new(100, "USD").allocate([1, 1, 1]) #=> [Money.new(34), Money.new(33), Money.new(33)]
580
+ # Money.new(100, "USD").allocate(2) #=> [Money.new(50), Money.new(50)]
581
+ # Money.new(100, "USD").allocate(3) #=> [Money.new(34), Money.new(33), Money.new(33)]
582
+ #
583
+ def allocate(parts)
584
+ amounts = Money::Allocation.generate(fractional, parts, !Money.default_infinite_precision)
585
+ amounts.map { |amount| dup_with(fractional: amount) }
586
+ end
587
+ alias_method :split, :allocate
588
+
589
+ # Round the monetary amount to smallest unit of coinage.
590
+ #
591
+ # @note
592
+ # This method is only useful when operating with infinite_precision turned
593
+ # on. Without infinite_precision values are rounded to the smallest unit of
594
+ # coinage automatically.
595
+ #
596
+ # @return [Money]
597
+ #
598
+ # @example
599
+ # Money.new(10.1, 'USD').round #=> Money.new(10, 'USD')
600
+ #
601
+ # @see Money.default_infinite_precision
602
+ def round(rounding_mode = self.class.rounding_mode, rounding_precision = 0)
603
+ rounded_amount = as_d(@fractional).round(rounding_precision, rounding_mode)
604
+ dup_with(fractional: rounded_amount)
605
+ end
606
+
607
+ # Creates a formatted price string according to several rules.
608
+ #
609
+ # @param [Hash] rules See {Money::Formatter Money::Formatter} for the list of formatting options
610
+ #
611
+ # @return [String]
612
+ #
613
+ def format(*rules)
614
+ Money::Formatter.new(self, *rules).to_s
615
+ end
616
+
617
+ # Returns a thousands separator according to the locale
618
+ #
619
+ # @return [String]
620
+ #
621
+ def thousands_separator
622
+ (locale_backend && locale_backend.lookup(:thousands_separator, currency)) ||
623
+ Money::Formatter::DEFAULTS[:thousands_separator]
624
+ end
625
+
626
+ # Returns a decimal mark according to the locale
627
+ #
628
+ # @return [String]
629
+ #
630
+ def decimal_mark
631
+ (locale_backend && locale_backend.lookup(:decimal_mark, currency)) ||
632
+ Money::Formatter::DEFAULTS[:decimal_mark]
633
+ end
634
+
635
+ def dup_with(options = {})
636
+ self.class.new(
637
+ options[:fractional] || fractional,
638
+ options[:currency] || currency,
639
+ bank: options[:bank] || bank
640
+ )
641
+ end
642
+
643
+ private
644
+
645
+ def as_d(num)
646
+ if num.respond_to?(:to_d)
647
+ num.is_a?(Rational) ? num.to_d(self.class.conversion_precision) : num.to_d
648
+ else
649
+ BigDecimal(num.to_s.empty? ? 0 : num.to_s)
650
+ end
651
+ end
652
+
653
+ def return_value(value)
654
+ if self.class.default_infinite_precision
655
+ value
656
+ else
657
+ value.round(0, self.class.rounding_mode).to_i
658
+ end
659
+ end
660
+
661
+ def locale_backend
662
+ self.class.locale_backend
663
+ end
664
+ end