bigdecimal-pure 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +8 -0
- data/LICENSE +21 -0
- data/README.md +55 -0
- data/lib/bigdecimal/math.rb +7 -0
- data/lib/bigdecimal/pure.rb +327 -0
- data/lib/bigdecimal/util.rb +7 -0
- data/lib/bigdecimal.rb +7 -0
- data/lib/bigdecimal_pure/version.rb +5 -0
- data/lib/bigdecimal_pure.rb +64 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dc0062663b0a3bfb0ae8756397f9e02443c49dacb965a6a3da25e01d39c7554a
|
|
4
|
+
data.tar.gz: 703b2fb877149972cf23ad3f5b0791510d4f5ed6932f3c8f5134d8c0053a37cd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2e60b3c1dc4e26635a84add8eabbdfecb67007be7764318fbbcaaa38c3b9df14129c16e9686a090bbf5848c4b2501e3f629c03c1cdb72c96ab46e5b26bb8b9eb
|
|
7
|
+
data.tar.gz: 9e23c4e38485648472c94c441199de7cc6a306b014d5b2653fa899a257b6c2a258716e512704e1dcc4b38fc833d49db3c2d82e8c5ebacf5e192f5f621c62b7aa
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
- First release: BigDecimal in pure Ruby on Rational, taken from the ruby.wasm
|
|
6
|
+
sandbox of Ren. `require 'bigdecimal'` prefers the native gem when installed.
|
|
7
|
+
|
|
8
|
+
[0.1.0]: https://github.com/Largo/bigdecimal-pure/releases/tag/v0.1.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# bigdecimal-pure
|
|
2
|
+
|
|
3
|
+
BigDecimal in pure Ruby, for places where the C extension cannot run —
|
|
4
|
+
ruby.wasm above all. Where the native `bigdecimal` gem is installed, it wins
|
|
5
|
+
automatically.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
gem 'bigdecimal-pure'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
require 'bigdecimal' # native if available, pure Ruby otherwise
|
|
13
|
+
require 'bigdecimal/util' # to_d, either way
|
|
14
|
+
|
|
15
|
+
BigDecimal('0.1') + BigDecimal('0.2') # => 0.3e0
|
|
16
|
+
BigDecimalPure.pure? # which one you got
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`require 'bigdecimal/pure'` loads the pure class unconditionally (unless a
|
|
20
|
+
BigDecimal is already defined). `BIGDECIMAL_PURE=1` makes `require 'bigdecimal'`
|
|
21
|
+
skip the native one.
|
|
22
|
+
|
|
23
|
+
## How the native one is found
|
|
24
|
+
|
|
25
|
+
`require 'bigdecimal'` activates the `bigdecimal` gem if it is installed (under
|
|
26
|
+
Bundler: if it is in the bundle), then loads the first `bigdecimal.rb` on the
|
|
27
|
+
load path that is not this gem's; on old Rubies it falls back to
|
|
28
|
+
`bigdecimal.so`. Only when all of that fails does the pure class load.
|
|
29
|
+
|
|
30
|
+
## What the pure class does
|
|
31
|
+
|
|
32
|
+
Built on `Rational`, so it is exact where BigDecimal is exact:
|
|
33
|
+
|
|
34
|
+
- decimal strings (`"0.1" + "0.2"` is `0.3`), `+ - *`, rounding;
|
|
35
|
+
- division and `sqrt` to 20 significant digits by default, like the original;
|
|
36
|
+
- `round/floor/ceil/truncate` with digits and all `ROUND_*` modes;
|
|
37
|
+
- `to_s` as `0.123e1`, `to_s('F')` as `1.23`; comparisons with Integer, Float
|
|
38
|
+
and Rational; `to_d` from `bigdecimal/util`; `Marshal`.
|
|
39
|
+
|
|
40
|
+
Not supported: NaN and Infinity (they raise), `BigDecimal.limit` and `.mode`,
|
|
41
|
+
precision bookkeeping. `BigMath` goes through Float, so it is good to about 15
|
|
42
|
+
digits whatever precision is asked for.
|
|
43
|
+
|
|
44
|
+
Unlike [hanami-wasm's shim](https://github.com/katafrakt/hanami-wasm)
|
|
45
|
+
(`BigDecimal(v) = v.to_i`), the value is kept: `BigDecimal("0.1")` is not 0.
|
|
46
|
+
|
|
47
|
+
## Tests
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
bundle exec rake
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# `require 'bigdecimal/math'`: the native file with the native bigdecimal. The
|
|
4
|
+
# pure class defines everything of bigdecimal/math itself.
|
|
5
|
+
require_relative '../bigdecimal'
|
|
6
|
+
|
|
7
|
+
BigDecimalPure.require_native('bigdecimal/math') unless BigDecimalPure.pure?
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# BigDecimal in pure Ruby, on top of Rational. lib/bigdecimal.rb loads this only
|
|
4
|
+
# when no native bigdecimal can be found (ruby.wasm, sandboxes without a
|
|
5
|
+
# compiler). Require it directly to get the pure version regardless.
|
|
6
|
+
#
|
|
7
|
+
# Like the C version: decimal strings exactly ("0.1" + "0.2" is 0.3), + - *
|
|
8
|
+
# exactly, division and sqrt to 20 significant digits by default,
|
|
9
|
+
# round/floor/ceil/truncate with digits and the ROUND_* modes, to_s ("0.3e0",
|
|
10
|
+
# or "0.3" with to_s('F')), comparisons with Integer, Float and Rational, to_d.
|
|
11
|
+
# Not: NaN and Infinity (they raise), BigDecimal.limit and .mode, precision
|
|
12
|
+
# bookkeeping. BigMath goes through Float, so it is good to about 15 digits.
|
|
13
|
+
|
|
14
|
+
require_relative '../bigdecimal_pure/version'
|
|
15
|
+
|
|
16
|
+
return if defined?(BigDecimal)
|
|
17
|
+
|
|
18
|
+
# rubocop:disable Lint/BigDecimalNew, Naming/PredicateMethod, Style/ReturnNilInPredicateMethodDefinition
|
|
19
|
+
# -- this is BigDecimal: .new is its own constructor, and infinite?/nonzero? answer
|
|
20
|
+
# nil as the C version does.
|
|
21
|
+
class BigDecimal < Numeric
|
|
22
|
+
ROUND_MODE = 256
|
|
23
|
+
ROUND_UP = 1
|
|
24
|
+
ROUND_DOWN = 2
|
|
25
|
+
ROUND_HALF_UP = 3
|
|
26
|
+
ROUND_HALF_DOWN = 4
|
|
27
|
+
ROUND_CEILING = 5
|
|
28
|
+
ROUND_FLOOR = 6
|
|
29
|
+
ROUND_HALF_EVEN = 7
|
|
30
|
+
SIGN_POSITIVE_ZERO = 1
|
|
31
|
+
SIGN_NEGATIVE_ZERO = -1
|
|
32
|
+
SIGN_POSITIVE_FINITE = 2
|
|
33
|
+
SIGN_NEGATIVE_FINITE = -2
|
|
34
|
+
DIV_DIGITS = 20
|
|
35
|
+
VERSION = BigDecimalPure::VERSION
|
|
36
|
+
PURE = true
|
|
37
|
+
|
|
38
|
+
DECIMAL = /\A\s*([+-]?)(\d[\d_]*)?(?:\.(\d[\d_]*)?)?(?:[eEdD]([+-]?\d+))?\s*\z/
|
|
39
|
+
|
|
40
|
+
attr_reader :rational
|
|
41
|
+
protected :rational
|
|
42
|
+
|
|
43
|
+
def self.parse(text)
|
|
44
|
+
match = DECIMAL.match(text)
|
|
45
|
+
if match.nil? || (match[2].nil? && match[3].nil?)
|
|
46
|
+
raise ArgumentError, "invalid value for BigDecimal(): #{text.inspect}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
whole = match[2].to_s.delete('_')
|
|
50
|
+
fraction = match[3].to_s.delete('_')
|
|
51
|
+
digits = Integer("#{whole}#{fraction}".sub(/\A0+(?=\d)/, '').then { |s| s.empty? ? '0' : s }, 10)
|
|
52
|
+
value = Rational(digits, 10**fraction.length) * (Rational(10)**match[4].to_i)
|
|
53
|
+
match[1] == '-' ? -value : value
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.from(value, digits = 0)
|
|
57
|
+
case value
|
|
58
|
+
when BigDecimal then value
|
|
59
|
+
when Integer then new(Rational(value))
|
|
60
|
+
when Rational then new(digits.positive? ? round_significant(value, digits) : value)
|
|
61
|
+
when Float
|
|
62
|
+
raise FloatDomainError, value.to_s unless value.finite?
|
|
63
|
+
|
|
64
|
+
new(digits.positive? ? round_significant(value.to_r, digits) : parse(value.to_s))
|
|
65
|
+
when String then new(parse(value))
|
|
66
|
+
else
|
|
67
|
+
raise TypeError, "can't convert #{value.class} into BigDecimal" unless value.respond_to?(:to_r)
|
|
68
|
+
|
|
69
|
+
new(value.to_r)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Rounds to `digits` significant digits, half up.
|
|
74
|
+
def self.round_significant(value, digits)
|
|
75
|
+
return Rational(0) if value.zero?
|
|
76
|
+
|
|
77
|
+
exponent = Math.log10(value.abs.to_f).floor rescue 0 # rubocop:disable Style/RescueModifier
|
|
78
|
+
scale = digits - exponent - 1
|
|
79
|
+
Rational((value * (Rational(10)**scale)).round, 1) / (Rational(10)**scale)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.double_fig = 16
|
|
83
|
+
def self.limit(*) = 0
|
|
84
|
+
def self.mode(*) = 0
|
|
85
|
+
def self.save_rounding_mode = yield
|
|
86
|
+
def self.save_exception_mode = yield
|
|
87
|
+
def self.save_limit = yield
|
|
88
|
+
|
|
89
|
+
def initialize(rational)
|
|
90
|
+
super()
|
|
91
|
+
@rational = rational
|
|
92
|
+
freeze
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def to_r = @rational
|
|
96
|
+
def to_i = @rational.truncate
|
|
97
|
+
alias to_int to_i
|
|
98
|
+
def to_f = @rational.to_f
|
|
99
|
+
def to_d = self
|
|
100
|
+
def zero? = @rational.zero?
|
|
101
|
+
def nonzero? = zero? ? nil : self
|
|
102
|
+
def negative? = @rational.negative?
|
|
103
|
+
def positive? = @rational.positive?
|
|
104
|
+
def finite? = true
|
|
105
|
+
def infinite? = nil
|
|
106
|
+
def nan? = false
|
|
107
|
+
def integer? = false
|
|
108
|
+
def real? = true
|
|
109
|
+
def hash = @rational.hash
|
|
110
|
+
def precs = [digits_of.length, digits_of.length + DIV_DIGITS]
|
|
111
|
+
def precision = digits_of.length
|
|
112
|
+
def exponent = zero? ? 0 : Math.log10(@rational.abs.to_f).floor + 1
|
|
113
|
+
|
|
114
|
+
def sign
|
|
115
|
+
if zero? then SIGN_POSITIVE_ZERO
|
|
116
|
+
else negative? ? SIGN_NEGATIVE_FINITE : SIGN_POSITIVE_FINITE
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def coerce(other) = [BigDecimal.from(other), self]
|
|
121
|
+
def -@ = BigDecimal.new(-@rational)
|
|
122
|
+
def +@ = self
|
|
123
|
+
def abs = BigDecimal.new(@rational.abs)
|
|
124
|
+
|
|
125
|
+
def +(other) = BigDecimal.new(@rational + value_of(other))
|
|
126
|
+
def -(other) = BigDecimal.new(@rational - value_of(other))
|
|
127
|
+
def *(other) = BigDecimal.new(@rational * value_of(other))
|
|
128
|
+
|
|
129
|
+
def /(other)
|
|
130
|
+
divisor = value_of(other)
|
|
131
|
+
raise ZeroDivisionError, 'divided by 0' if divisor.zero?
|
|
132
|
+
|
|
133
|
+
exact = @rational / divisor
|
|
134
|
+
terminating?(exact) ? BigDecimal.new(exact) : BigDecimal.new(BigDecimal.round_significant(exact, DIV_DIGITS))
|
|
135
|
+
end
|
|
136
|
+
alias quo /
|
|
137
|
+
|
|
138
|
+
def add(other, digits) = limit(self + other, digits)
|
|
139
|
+
def sub(other, digits) = limit(self - other, digits)
|
|
140
|
+
def mult(other, digits) = limit(self * other, digits)
|
|
141
|
+
|
|
142
|
+
def div(other, digits = nil)
|
|
143
|
+
return (self / other).floor if digits.nil?
|
|
144
|
+
|
|
145
|
+
divisor = value_of(other)
|
|
146
|
+
raise ZeroDivisionError, 'divided by 0' if divisor.zero?
|
|
147
|
+
|
|
148
|
+
digits.zero? ? self / other : BigDecimal.new(BigDecimal.round_significant(@rational / divisor, digits))
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def %(other) = BigDecimal.new(@rational % value_of(other))
|
|
152
|
+
alias modulo %
|
|
153
|
+
def remainder(other) = BigDecimal.new(@rational.remainder(value_of(other)))
|
|
154
|
+
def divmod(other) = [div(other), self % other]
|
|
155
|
+
|
|
156
|
+
def **(other)
|
|
157
|
+
return BigDecimal.new(@rational**other) if other.is_a?(Integer)
|
|
158
|
+
|
|
159
|
+
BigDecimal.from(to_f**other.to_f, DIV_DIGITS)
|
|
160
|
+
end
|
|
161
|
+
alias power **
|
|
162
|
+
|
|
163
|
+
def sqrt(digits = DIV_DIGITS)
|
|
164
|
+
raise FloatDomainError, 'sqrt of negative value' if negative?
|
|
165
|
+
|
|
166
|
+
BigDecimal.from(Math.sqrt(to_f), [digits, 15].min)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def <=>(other)
|
|
170
|
+
other.is_a?(Numeric) ? @rational <=> value_of(other) : nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def ==(other) = other.is_a?(Numeric) && @rational == value_of(other)
|
|
174
|
+
alias eql? ==
|
|
175
|
+
alias === ==
|
|
176
|
+
include Comparable
|
|
177
|
+
|
|
178
|
+
# Without digits an Integer, as in the C version; with digits a BigDecimal.
|
|
179
|
+
def round(*args, half: nil)
|
|
180
|
+
digits = args[0].is_a?(Integer) ? args[0] : 0
|
|
181
|
+
half = { up: ROUND_HALF_UP, down: ROUND_HALF_DOWN, even: ROUND_HALF_EVEN }[half]
|
|
182
|
+
mode = args.find { |arg| arg.is_a?(Symbol) } || (args[1].is_a?(Integer) ? args[1] : nil) || half || ROUND_HALF_UP
|
|
183
|
+
rounded = round_to(digits, mode)
|
|
184
|
+
args.empty? ? rounded.to_i : rounded
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def floor(digits = 0) = digits.zero? ? @rational.floor : round_to(digits, ROUND_FLOOR)
|
|
188
|
+
def ceil(digits = 0) = digits.zero? ? @rational.ceil : round_to(digits, ROUND_CEILING)
|
|
189
|
+
def truncate(digits = 0) = digits.zero? ? @rational.truncate : round_to(digits, ROUND_DOWN)
|
|
190
|
+
alias fix truncate
|
|
191
|
+
|
|
192
|
+
def frac = BigDecimal.new(@rational - @rational.truncate)
|
|
193
|
+
|
|
194
|
+
# "0.123e1" like the C version; to_s('F') for plain notation.
|
|
195
|
+
def to_s(format = nil)
|
|
196
|
+
return plain if format.to_s.upcase.include?('F')
|
|
197
|
+
return '0.0' if zero?
|
|
198
|
+
|
|
199
|
+
digits = digits_of
|
|
200
|
+
"#{'-' if negative?}0.#{digits}e#{exponent_of}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def inspect = to_s
|
|
204
|
+
def to_digits = plain
|
|
205
|
+
|
|
206
|
+
def _dump(_level = 0) = to_s
|
|
207
|
+
def self._load(text) = from(text)
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
def value_of(other)
|
|
212
|
+
case other
|
|
213
|
+
when BigDecimal then other.rational
|
|
214
|
+
when Integer, Rational then other.to_r
|
|
215
|
+
when Float
|
|
216
|
+
raise FloatDomainError, other.to_s unless other.finite?
|
|
217
|
+
|
|
218
|
+
BigDecimal.parse(other.to_s)
|
|
219
|
+
else
|
|
220
|
+
raise TypeError, "#{other.class} can't be coerced into BigDecimal" unless other.respond_to?(:to_r)
|
|
221
|
+
|
|
222
|
+
other.to_r
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def limit(value, digits)
|
|
227
|
+
digits.to_i.positive? ? BigDecimal.new(BigDecimal.round_significant(value.to_r, digits.to_i)) : value
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def terminating?(value)
|
|
231
|
+
denominator = value.denominator
|
|
232
|
+
denominator /= 2 while (denominator % 2).zero?
|
|
233
|
+
denominator /= 5 while (denominator % 5).zero?
|
|
234
|
+
denominator == 1
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# All significant digits, without leading or trailing zeros.
|
|
238
|
+
def digits_of
|
|
239
|
+
text = plain.delete('-').delete('.').sub(/\A0+/, '').sub(/0+\z/, '')
|
|
240
|
+
text.empty? ? '0' : text
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def exponent_of
|
|
244
|
+
whole, fraction = plain.delete('-').split('.')
|
|
245
|
+
return whole.length if whole != '0'
|
|
246
|
+
|
|
247
|
+
-(fraction.length - fraction.sub(/\A0+/, '').length)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Plain decimal notation. Every value here terminates: strings, Integers and
|
|
251
|
+
# Floats parse to finite decimals, and division rounds when it would not.
|
|
252
|
+
def plain
|
|
253
|
+
value = @rational.abs
|
|
254
|
+
scale = 0
|
|
255
|
+
scale += 1 until (value * (10**scale)).denominator == 1 || scale > 1000
|
|
256
|
+
digits = (value * (10**scale)).round.to_s.rjust(scale + 1, '0')
|
|
257
|
+
whole = digits[0...(digits.length - scale)]
|
|
258
|
+
fraction = scale.zero? ? '0' : digits[(digits.length - scale)..].sub(/0+\z/, '')
|
|
259
|
+
fraction = '0' if fraction.empty?
|
|
260
|
+
"#{'-' if negative?}#{whole}.#{fraction}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def round_to(digits, mode)
|
|
264
|
+
scale = Rational(10)**digits
|
|
265
|
+
scaled = @rational * scale
|
|
266
|
+
whole = case mode
|
|
267
|
+
when ROUND_UP, :up then scaled.negative? ? scaled.floor : scaled.ceil
|
|
268
|
+
when ROUND_DOWN, :down, :truncate then scaled.truncate
|
|
269
|
+
when ROUND_CEILING, :ceiling, :ceil then scaled.ceil
|
|
270
|
+
when ROUND_FLOOR, :floor then scaled.floor
|
|
271
|
+
when ROUND_HALF_DOWN, :half_down then half(scaled, :down)
|
|
272
|
+
when ROUND_HALF_EVEN, :half_even, :banker then half(scaled, :even)
|
|
273
|
+
else scaled.round(half: :up)
|
|
274
|
+
end
|
|
275
|
+
BigDecimal.new(Rational(whole) / scale)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def half(scaled, way) = scaled.round(half: way)
|
|
279
|
+
end
|
|
280
|
+
# rubocop:enable Lint/BigDecimalNew, Naming/PredicateMethod, Style/ReturnNilInPredicateMethodDefinition
|
|
281
|
+
|
|
282
|
+
module Kernel
|
|
283
|
+
def BigDecimal(value, digits = 0, exception: true) # rubocop:disable Naming/MethodName
|
|
284
|
+
BigDecimal.from(value, digits)
|
|
285
|
+
rescue ArgumentError, TypeError
|
|
286
|
+
raise if exception
|
|
287
|
+
|
|
288
|
+
nil
|
|
289
|
+
end
|
|
290
|
+
module_function :BigDecimal
|
|
291
|
+
public :BigDecimal
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# bigdecimal/util: to_d on the numbers and strings.
|
|
295
|
+
class Integer
|
|
296
|
+
def to_d = BigDecimal(self)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
class Float
|
|
300
|
+
def to_d(digits = 0) = BigDecimal(self, digits)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
class Rational
|
|
304
|
+
def to_d(digits) = BigDecimal(self, digits)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
class String
|
|
308
|
+
def to_d = BigDecimal(self, exception: false) || BigDecimal(0)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
class NilClass
|
|
312
|
+
def to_d = BigDecimal(0)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# bigdecimal/math, through Float: about 15 good digits whatever is asked for.
|
|
316
|
+
module BigMath
|
|
317
|
+
module_function
|
|
318
|
+
|
|
319
|
+
def exp(value, digits) = BigDecimal(Math.exp(value.to_f), [digits, 15].min)
|
|
320
|
+
def log(value, digits) = BigDecimal(Math.log(value.to_f), [digits, 15].min)
|
|
321
|
+
def sqrt(value, digits) = BigDecimal(value).sqrt(digits)
|
|
322
|
+
def PI(digits) = BigDecimal(Math::PI, [digits, 15].min) # rubocop:disable Naming/MethodName
|
|
323
|
+
def E(digits) = BigDecimal(Math::E, [digits, 15].min) # rubocop:disable Naming/MethodName
|
|
324
|
+
def sin(value, digits) = BigDecimal(Math.sin(value.to_f), [digits, 15].min)
|
|
325
|
+
def cos(value, digits) = BigDecimal(Math.cos(value.to_f), [digits, 15].min)
|
|
326
|
+
def atan(value, digits) = BigDecimal(Math.atan(value.to_f), [digits, 15].min)
|
|
327
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# `require 'bigdecimal/util'`: the native file with the native bigdecimal. The
|
|
4
|
+
# pure class defines everything of bigdecimal/util itself.
|
|
5
|
+
require_relative '../bigdecimal'
|
|
6
|
+
|
|
7
|
+
BigDecimalPure.require_native('bigdecimal/util') unless BigDecimalPure.pure?
|
data/lib/bigdecimal.rb
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# `require 'bigdecimal'`: the native bigdecimal when it is installed, else the
|
|
4
|
+
# pure Ruby one from this gem.
|
|
5
|
+
require_relative 'bigdecimal_pure'
|
|
6
|
+
|
|
7
|
+
require_relative 'bigdecimal/pure' unless defined?(BigDecimal) || BigDecimalPure.require_native('bigdecimal')
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'bigdecimal_pure/version'
|
|
4
|
+
|
|
5
|
+
# Finds the native bigdecimal, if there is one, so that `require 'bigdecimal'`
|
|
6
|
+
# gets it and this gem's pure Ruby class only fills in where it is missing.
|
|
7
|
+
module BigDecimalPure
|
|
8
|
+
OWN_LIB = File.expand_path(__dir__)
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# true once the pure class is the BigDecimal in use.
|
|
13
|
+
def pure? = defined?(::BigDecimal::PURE) ? true : false
|
|
14
|
+
|
|
15
|
+
# true when the native extension is the BigDecimal in use.
|
|
16
|
+
def native? = defined?(::BigDecimal) && !pure? ? true : false
|
|
17
|
+
|
|
18
|
+
# Loads the native `feature` ("bigdecimal", "bigdecimal/util", ...), or
|
|
19
|
+
# returns false when there is none. BIGDECIMAL_PURE=1 forces false.
|
|
20
|
+
def require_native(feature)
|
|
21
|
+
return false if ENV['BIGDECIMAL_PURE'] == '1'
|
|
22
|
+
|
|
23
|
+
activate_native_gem
|
|
24
|
+
path = native_path(feature)
|
|
25
|
+
if path
|
|
26
|
+
require path
|
|
27
|
+
true
|
|
28
|
+
elsif feature == 'bigdecimal'
|
|
29
|
+
require_native_extension
|
|
30
|
+
else
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The bigdecimal gem, installed next to this one: put its lib on the load
|
|
36
|
+
# path. Under Bundler this only works if it is in the bundle, which is right.
|
|
37
|
+
def activate_native_gem
|
|
38
|
+
return unless defined?(Gem) && Gem.respond_to?(:try_activate)
|
|
39
|
+
|
|
40
|
+
gem 'bigdecimal'
|
|
41
|
+
rescue LoadError, StandardError
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# "<feature>.rb" anywhere on the load path except in this gem.
|
|
46
|
+
def native_path(feature)
|
|
47
|
+
$LOAD_PATH.each do |dir|
|
|
48
|
+
dir = File.expand_path(dir.to_s)
|
|
49
|
+
next if dir == OWN_LIB
|
|
50
|
+
|
|
51
|
+
candidate = File.join(dir, "#{feature}.rb")
|
|
52
|
+
return candidate if File.file?(candidate)
|
|
53
|
+
end
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Old Rubies ship only bigdecimal.so, without a Ruby part.
|
|
58
|
+
def require_native_extension
|
|
59
|
+
require 'bigdecimal.so'
|
|
60
|
+
true
|
|
61
|
+
rescue LoadError
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bigdecimal-pure
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andi
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: "`require 'bigdecimal'` loads the native bigdecimal when it is installed
|
|
13
|
+
and a pure Ruby BigDecimal on Rational otherwise, for ruby.wasm and other places
|
|
14
|
+
without a C compiler."
|
|
15
|
+
email:
|
|
16
|
+
- andi@idogawa.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/bigdecimal.rb
|
|
25
|
+
- lib/bigdecimal/math.rb
|
|
26
|
+
- lib/bigdecimal/pure.rb
|
|
27
|
+
- lib/bigdecimal/util.rb
|
|
28
|
+
- lib/bigdecimal_pure.rb
|
|
29
|
+
- lib/bigdecimal_pure/version.rb
|
|
30
|
+
homepage: https://github.com/Largo/bigdecimal-pure
|
|
31
|
+
licenses:
|
|
32
|
+
- MIT
|
|
33
|
+
metadata:
|
|
34
|
+
homepage_uri: https://github.com/Largo/bigdecimal-pure
|
|
35
|
+
changelog_uri: https://github.com/Largo/bigdecimal-pure/blob/main/CHANGELOG.md
|
|
36
|
+
rubygems_mfa_required: 'true'
|
|
37
|
+
rdoc_options: []
|
|
38
|
+
require_paths:
|
|
39
|
+
- lib
|
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: '3.1'
|
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '0'
|
|
50
|
+
requirements: []
|
|
51
|
+
rubygems_version: 4.0.20
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: BigDecimal in pure Ruby, used only where the native one is missing.
|
|
54
|
+
test_files: []
|