bigdecimal-pure 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +64 -14
- data/lib/bigdecimal/pure.rb +9 -319
- data/lib/bigdecimal_pure/calc.rb +582 -0
- data/lib/bigdecimal_pure/decimal.rb +1479 -0
- data/lib/bigdecimal_pure/math.rb +661 -0
- data/lib/bigdecimal_pure/util.rb +44 -0
- data/lib/bigdecimal_pure/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf29bb03b15bb0bb1cbe340b2bc5a861f691af86d725f0d05122874ed2daeb64
|
|
4
|
+
data.tar.gz: 604104049ea023a06615056a809cd806ac446e7214d2ac8164002b4a232068ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b15e47a6978178e1199b4d51403b5fb5dfe3e9728d5ec7e165c358a91bd956b31e391a48708e94c2225079790797d5840e2e32e92969c35c78900ec5c34d4180
|
|
7
|
+
data.tar.gz: ea5fe695be57c79f4d3e45c5a609ffc8fec9464f025059c15dcedf8ed625dbc3ced159b5c0a7717161fa21295f741f191d4b987422571b8326e737cc45e15696
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026-09-30
|
|
4
|
+
|
|
5
|
+
The pure class now does what bigdecimal 4.x does, where 0.1.0 did not:
|
|
6
|
+
|
|
7
|
+
- NaN, `Infinity`, `-Infinity` and `-0`, with `BigDecimal::NAN` and
|
|
8
|
+
`BigDecimal::INFINITY`. `1 / 0` is `Infinity` and `0 / 0` is `NaN`, as in
|
|
9
|
+
the C version; 0.1.0 raised `ZeroDivisionError`.
|
|
10
|
+
- `BigDecimal.mode` for the exception modes and the rounding mode, and
|
|
11
|
+
`BigDecimal.limit`, per thread, with the `save_*` blocks.
|
|
12
|
+
- The precision of each result as in C: `/` keeps the larger operand's
|
|
13
|
+
precision plus 16 digits, at least 32 (0.1.0 used 20); Rational operands;
|
|
14
|
+
`add/sub/mult/div` with a precision; `sqrt(0)`; `power`.
|
|
15
|
+
- `BigMath` to any precision; 0.1.0 went through Float and was good to about
|
|
16
|
+
15 digits. Also the functions bigdecimal 4.x added: `cbrt`, `hypot`, `tan`,
|
|
17
|
+
`asin`, `acos`, `atan2`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`,
|
|
18
|
+
`log2`, `log10`, `log1p`, `expm1`, `erf`, `erfc`, `gamma`, `lgamma`,
|
|
19
|
+
`frexp`, `ldexp`.
|
|
20
|
+
- `BigDecimal(float, digits)`, division and `ROUND_UP` round exactly as the C
|
|
21
|
+
version does, down to its 9-digit words.
|
|
22
|
+
- `BigDecimal.interpret_loosely`, every `to_s` format, `precision_scale`,
|
|
23
|
+
`n_significant_digits`, `split`, `_decimal_shift`.
|
|
24
|
+
- As in 4.x, `sqrt` needs its precision and `precs` is gone.
|
|
25
|
+
|
|
26
|
+
`test/compare_test.rb` runs about 8,500 expressions with the native and the
|
|
27
|
+
pure class and compares every result; `test/wasm_test.rb` runs them in
|
|
28
|
+
ruby.wasm.
|
|
29
|
+
|
|
3
30
|
## [0.1.0] - 2026-09-30
|
|
4
31
|
|
|
5
32
|
- First release: BigDecimal in pure Ruby on Rational, taken from the ruby.wasm
|
|
6
33
|
sandbox of Ren. `require 'bigdecimal'` prefers the native gem when installed.
|
|
7
34
|
|
|
35
|
+
[0.2.0]: https://github.com/Largo/bigdecimal-pure/releases/tag/v0.2.0
|
|
8
36
|
[0.1.0]: https://github.com/Largo/bigdecimal-pure/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -11,8 +11,10 @@ gem 'bigdecimal-pure'
|
|
|
11
11
|
```ruby
|
|
12
12
|
require 'bigdecimal' # native if available, pure Ruby otherwise
|
|
13
13
|
require 'bigdecimal/util' # to_d, either way
|
|
14
|
+
require 'bigdecimal/math' # BigMath, either way
|
|
14
15
|
|
|
15
16
|
BigDecimal('0.1') + BigDecimal('0.2') # => 0.3e0
|
|
17
|
+
BigMath.PI(50) # => 0.31415926535897932384626433832795028841971693993751e1
|
|
16
18
|
BigDecimalPure.pure? # which one you got
|
|
17
19
|
```
|
|
18
20
|
|
|
@@ -29,20 +31,61 @@ load path that is not this gem's; on old Rubies it falls back to
|
|
|
29
31
|
|
|
30
32
|
## What the pure class does
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- decimal strings (`"0.1" + "0.2"` is `0.3`),
|
|
35
|
-
|
|
36
|
-
- `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
precision
|
|
42
|
-
digits
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
What bigdecimal 4.x does, with the same results:
|
|
35
|
+
|
|
36
|
+
- decimal strings exactly (`"0.1" + "0.2"` is `0.3`), with the same grammar
|
|
37
|
+
as the C version, `BigDecimal.interpret_loosely` and `String#to_d`;
|
|
38
|
+
- NaN, `Infinity`, `-Infinity` and `-0`: `1 / 0` is `Infinity`, `0 / 0` is
|
|
39
|
+
`NaN`, and `BigDecimal.mode` turns each of them into an exception;
|
|
40
|
+
- `BigDecimal.mode(BigDecimal::ROUND_MODE, ...)` and `BigDecimal.limit`, per
|
|
41
|
+
thread, with `save_exception_mode`, `save_rounding_mode` and `save_limit`;
|
|
42
|
+
- the precision of each result: `+ - *` are exact unless a limit or a
|
|
43
|
+
precision argument says otherwise, `/` keeps the larger operand's precision
|
|
44
|
+
plus 16 digits (at least 32), a Rational operand is converted to as many
|
|
45
|
+
digits as the C version uses, `add/sub/mult/div(x, digits)` round to that;
|
|
46
|
+
- `round/floor/ceil/truncate` with digits, every `ROUND_*` mode and `half:`;
|
|
47
|
+
`%`, `divmod`, `remainder`; `**` and `power`, exact for Integer powers;
|
|
48
|
+
`sqrt(digits)`, exact when the root is;
|
|
49
|
+
- `to_s` in every format (`'F'`, `'E'`, digit groups, `'+'`, `' '`),
|
|
50
|
+
`precision`, `scale`, `n_significant_digits`, `exponent`, `split`,
|
|
51
|
+
`_dump`/`_load` for `Marshal`, comparisons and coercion with Integer, Float,
|
|
52
|
+
Rational and Complex;
|
|
53
|
+
- `BigDecimal(float, digits)` digit for digit as the C version, which rounds
|
|
54
|
+
with the dtoa of Ruby and inherits its one-half shortcut;
|
|
55
|
+
- `BigMath` to any precision: `exp`, `log`, `log2`, `log10`, `log1p`, `expm1`,
|
|
56
|
+
`sqrt`, `cbrt`, `hypot`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`,
|
|
57
|
+
`sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `erf`, `erfc`, `gamma`,
|
|
58
|
+
`lgamma`, `frexp`, `ldexp`, `PI` and `E`.
|
|
59
|
+
|
|
60
|
+
The C version keeps its digits in 9-digit words, and a few results show it:
|
|
61
|
+
the precision of a Rational operand, the prefix of `_dump`, `ROUND_UP` to a
|
|
62
|
+
position far left of the number, and division, which rounds from the words
|
|
63
|
+
it has computed. The pure class counts those words too.
|
|
64
|
+
|
|
65
|
+
### Where the two can differ
|
|
66
|
+
|
|
67
|
+
- `BigMath` computes each result with 24 more digits and rounds once; the C
|
|
68
|
+
version rounds after every step and carries 16. When the exact value lies
|
|
69
|
+
closer than about 10**-(prec + 16) to a number of `prec` digits — in a
|
|
70
|
+
rounding mode other than the half modes, or for an argument so small that
|
|
71
|
+
the result is nearly the argument or nearly 1 — the last digit can differ
|
|
72
|
+
by one. Where that happened in the tests, the pure result was the correct
|
|
73
|
+
one: `BigMath.cos(0, 10)` in `ROUND_DOWN` is `1`, the C version says
|
|
74
|
+
`0.9999999999`.
|
|
75
|
+
- `hash` values differ, and the C version's debugging methods (`vpdivd`,
|
|
76
|
+
`vpmult`, ...) and its internal `BigDecimal::Internal` are not there.
|
|
77
|
+
- It is Ruby: basic operations take a few microseconds instead of a fraction
|
|
78
|
+
of one. BigMath works on Integers and keeps up with the C version's, which
|
|
79
|
+
is Ruby too: faster at 50 digits, about even at 500 (gamma a little
|
|
80
|
+
slower).
|
|
81
|
+
|
|
82
|
+
## Thanks
|
|
83
|
+
|
|
84
|
+
This gem only stands in where the real one cannot run. The real one is
|
|
85
|
+
[ruby/bigdecimal](https://github.com/ruby/bigdecimal), and its behaviour is
|
|
86
|
+
what this gem copies. Thank you to its authors and maintainers: Shigeo
|
|
87
|
+
Kobayashi, who wrote it, Kenta Murata, Zachary Scott, tompng, and everyone
|
|
88
|
+
who has contributed to it.
|
|
46
89
|
|
|
47
90
|
## Tests
|
|
48
91
|
|
|
@@ -50,6 +93,13 @@ Unlike [hanami-wasm's shim](https://github.com/katafrakt/hanami-wasm)
|
|
|
50
93
|
bundle exec rake
|
|
51
94
|
```
|
|
52
95
|
|
|
96
|
+
runs the pure class against known values, checks that the native one loads
|
|
97
|
+
when it is there, and runs about 8,500 expressions with both and compares
|
|
98
|
+
every result, error class and message (`test/compare_test.rb`; more with
|
|
99
|
+
`CASES_SEED=7 CASES_SCALE=5`). `bundle exec rake wasm` runs the same
|
|
100
|
+
expressions in ruby.wasm under node, with `RUBY_WASM_DIST` pointing to the
|
|
101
|
+
`dist` directory of the npm package `@ruby/4.0-wasm-wasi`.
|
|
102
|
+
|
|
53
103
|
## License
|
|
54
104
|
|
|
55
105
|
MIT
|
data/lib/bigdecimal/pure.rb
CHANGED
|
@@ -1,327 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# BigDecimal in pure Ruby
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# BigDecimal in pure Ruby. lib/bigdecimal.rb loads this only when no native
|
|
4
|
+
# bigdecimal can be found (ruby.wasm, sandboxes without a compiler). Require
|
|
5
|
+
# it directly to get the pure version regardless.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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.
|
|
7
|
+
# It behaves like bigdecimal 4.x: NaN, the infinities and -0, BigDecimal.mode
|
|
8
|
+
# and BigDecimal.limit, the precision rules of each operation, and BigMath to
|
|
9
|
+
# any precision. bigdecimal/util and bigdecimal/math come with it.
|
|
13
10
|
|
|
14
11
|
require_relative '../bigdecimal_pure/version'
|
|
15
12
|
|
|
16
13
|
return if defined?(BigDecimal)
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
15
|
+
require_relative '../bigdecimal_pure/decimal'
|
|
16
|
+
require_relative '../bigdecimal_pure/math'
|
|
17
|
+
require_relative '../bigdecimal_pure/util'
|