From 2ec99e92c2c8a59304062e176e3db20ce7e24112 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 20:34:04 +0900 Subject: [PATCH 001/144] Add version 3.0.0 in README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3fdaf698..a062bb71 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ The differences among versions are given below: | version | characteristics | Supported ruby version range | | ------- | --------------- | ----------------------- | +| 3.0.0 | You can use BigDecimal with Ractor on Ruby 3.0 | 2.5 .. | | 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | | 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.6 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | From 43990eee4097e61eb8bbff5e02f70d003dea7e51 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sun, 20 Dec 2020 03:13:33 +0900 Subject: [PATCH 002/144] bigdecimal: initialize conditionally assigned variable --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4e49de8e..7fdf4012 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -418,7 +418,7 @@ BigDecimal_precision(VALUE self) */ ssize_t ex = p->exponent; - ssize_t precision; + ssize_t precision = 0; if (ex < 0) { precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ ex = 0; From 94b60a10fd3bea61547678950c69594a2b51dd11 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 19 Dec 2020 21:13:33 +0900 Subject: [PATCH 003/144] [bigdecimal] Fix deprecation warning test --- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 445a3f2e..16f55057 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -611,9 +611,13 @@ def test_cmp_data end def test_precs_deprecated + saved = Warning[:deprecated] + Warning[:deprecated] = true assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do BigDecimal("1").precs end + ensure + Warning[:deprecated] = saved end def test_precs From e5e50f779a68dd4e00ab1e2e34f2332c810d50fe Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 26 Dec 2020 17:44:20 +0900 Subject: [PATCH 004/144] Fix for old versions of Ruby --- test/bigdecimal/test_bigdecimal.rb | 5 +- test/lib/envutil.rb | 248 ++++++++++++++++++++--------- test/lib/test/unit/assertions.rb | 222 ++++++++++++++++++-------- 3 files changed, 333 insertions(+), 142 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 16f55057..6ce9c3b8 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -611,13 +611,10 @@ def test_cmp_data end def test_precs_deprecated - saved = Warning[:deprecated] - Warning[:deprecated] = true assert_warn(/BigDecimal#precs is deprecated and will be removed in the future/) do + Warning[:deprecated] = true if defined?(Warning.[]) BigDecimal("1").precs end - ensure - Warning[:deprecated] = saved end def test_precs diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb index a0b907c2..937e1128 100644 --- a/test/lib/envutil.rb +++ b/test/lib/envutil.rb @@ -1,8 +1,16 @@ # -*- coding: us-ascii -*- -# frozen_string_literal: false +# frozen_string_literal: true require "open3" require "timeout" require_relative "find_executable" +begin + require 'rbconfig' +rescue LoadError +end +begin + require "rbconfig/sizeof" +rescue LoadError +end module EnvUtil def rubybin @@ -34,12 +42,95 @@ def rubybin DEFAULT_SIGNALS = Signal.list DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM + RUBYLIB = ENV["RUBYLIB"] + + class << self + attr_accessor :timeout_scale + attr_reader :original_internal_encoding, :original_external_encoding, + :original_verbose, :original_warning + + def capture_global_values + @original_internal_encoding = Encoding.default_internal + @original_external_encoding = Encoding.default_external + @original_verbose = $VERBOSE + @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil + end + end + + def apply_timeout_scale(t) + if scale = EnvUtil.timeout_scale + t * scale + else + t + end + end + module_function :apply_timeout_scale + + def timeout(sec, klass = nil, message = nil, &blk) + return yield(sec) if sec == nil or sec.zero? + sec = apply_timeout_scale(sec) + Timeout.timeout(sec, klass, message, &blk) + end + module_function :timeout + + def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) + reprieve = apply_timeout_scale(reprieve) if reprieve + + signals = Array(signal).select do |sig| + DEFAULT_SIGNALS[sig.to_s] or + DEFAULT_SIGNALS[Signal.signame(sig)] rescue false + end + signals |= [:ABRT, :KILL] + case pgroup + when 0, true + pgroup = -pid + when nil, false + pgroup = pid + end + + lldb = true if /darwin/ =~ RUBY_PLATFORM + + while signal = signals.shift + + if lldb and [:ABRT, :KILL].include?(signal) + lldb = false + # sudo -n: --non-interactive + # lldb -p: attach + # -o: run command + system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) + true + end + + begin + Process.kill signal, pgroup + rescue Errno::EINVAL + next + rescue Errno::ESRCH + break + end + if signals.empty? or !reprieve + Process.wait(pid) + else + begin + Timeout.timeout(reprieve) {Process.wait(pid)} + rescue Timeout::Error + else + break + end + end + end + $? + end + module_function :terminate + def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, stdout_filter: nil, stderr_filter: nil, signal: :TERM, - rubybin: EnvUtil.rubybin, + rubybin: EnvUtil.rubybin, precommand: nil, **opt) + timeout = apply_timeout_scale(timeout) + in_c, in_p = IO.pipe out_p, out_c = IO.pipe if capture_stdout err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout @@ -56,11 +147,17 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = if Array === args and Hash === args.first child_env.update(args.shift) end + if RUBYLIB and lib = child_env["RUBYLIB"] + child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) + end + child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] args = [args] if args.kind_of?(String) - pid = spawn(child_env, rubybin, *args, **opt) + pid = spawn(child_env, *precommand, rubybin, *args, **opt) in_c.close - out_c.close if capture_stdout - err_c.close if capture_stderr && capture_stderr != :merge_to_stdout + out_c&.close + out_c = nil + err_c&.close + err_c = nil if block_given? return yield in_p, out_p, err_p, pid else @@ -71,35 +168,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) timeout_error = nil else - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup = opt[:pgroup] - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - while signal = signals.shift - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - end - end - end - status = $? + status = terminate(pid, signal, opt[:pgroup], reprieve) + terminated = Time.now end stdout = th_stdout.value if capture_stdout stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout @@ -110,8 +180,8 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = stderr = stderr_filter.call(stderr) if stderr_filter if timeout_error bt = caller_locations - msg = "execution of #{bt.shift.label} expired" - msg = Test::Unit::Assertions::FailDesc[status, msg, [stdout, stderr].join("\n")].() + msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" + msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) raise timeout_error, msg, bt.map(&:to_s) end return stdout, stderr, status @@ -121,7 +191,7 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = th.kill if th end [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io.close if io && !io.closed? + io&.close end [th_stdout, th_stderr].each do |th| th.join if th @@ -129,36 +199,35 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = end module_function :invoke_ruby - alias rubyexec invoke_ruby - class << self - alias rubyexec invoke_ruby - end - def verbose_warning - class << (stderr = "") - alias write << + class << (stderr = "".dup) + alias write concat + def flush; end end - stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true + stderr, $stderr = $stderr, stderr + $VERBOSE = true yield stderr return $stderr ensure - stderr, $stderr, $VERBOSE = $stderr, stderr, verbose + stderr, $stderr = $stderr, stderr + $VERBOSE = EnvUtil.original_verbose + EnvUtil.original_warning&.each {|i, v| Warning[i] = v} end module_function :verbose_warning def default_warning - verbose, $VERBOSE = $VERBOSE, false + $VERBOSE = false yield ensure - $VERBOSE = verbose + $VERBOSE = EnvUtil.original_verbose end module_function :default_warning def suppress_warning - verbose, $VERBOSE = $VERBOSE, nil + $VERBOSE = nil yield ensure - $VERBOSE = verbose + $VERBOSE = EnvUtil.original_verbose end module_function :suppress_warning @@ -171,32 +240,28 @@ def under_gc_stress(stress = true) module_function :under_gc_stress def with_default_external(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_external = Encoding.default_external, enc - $VERBOSE = verbose + suppress_warning { Encoding.default_external = enc } yield ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_external = origenc - $VERBOSE = verbose + suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } end module_function :with_default_external def with_default_internal(enc) - verbose, $VERBOSE = $VERBOSE, nil - origenc, Encoding.default_internal = Encoding.default_internal, enc - $VERBOSE = verbose + suppress_warning { Encoding.default_internal = enc } yield ensure - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_internal = origenc - $VERBOSE = verbose + suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } end module_function :with_default_internal def labeled_module(name, &block) Module.new do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } class_eval(&block) if block end end @@ -204,7 +269,11 @@ def labeled_module(name, &block) def labeled_class(name, superclass = Object, &block) Class.new(superclass) do - singleton_class.class_eval {define_method(:to_s) {name}; alias inspect to_s} + singleton_class.class_eval { + define_method(:to_s) {name} + alias inspect to_s + alias name to_s + } class_eval(&block) if block end end @@ -213,9 +282,12 @@ def labeled_class(name, superclass = Object, &block) if /darwin/ =~ RUBY_PLATFORM DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - def self.diagnostic_reports(signame, cmd, pid, now) + @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + + def self.diagnostic_reports(signame, pid, now) return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(cmd) + cmd = File.basename(rubybin) + cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd path = DIAGNOSTIC_REPORTS_PATH timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" @@ -234,8 +306,39 @@ def self.diagnostic_reports(signame, cmd, pid, now) nil end else - def self.diagnostic_reports(signame, cmd, pid, now) + def self.diagnostic_reports(signame, pid, now) + end + end + + def self.failure_description(status, now, message = "", out = "") + pid = status.pid + if signo = status.termsig + signame = Signal.signame(signo) + sigdesc = "signal #{signo}" + end + log = diagnostic_reports(signame, pid, now) + if signame + sigdesc = "SIG#{signame} (#{sigdesc})" + end + if status.coredump? + sigdesc = "#{sigdesc} (core dumped)" end + full_message = ''.dup + message = message.call if Proc === message + if message and !message.empty? + full_message << message << "\n" + end + full_message << "pid #{pid}" + full_message << " exit #{status.exitstatus}" if status.exited? + full_message << " killed by #{sigdesc}" if sigdesc + if out and !out.empty? + full_message << "\n" << out.b.gsub(/^/, '| ') + full_message.sub!(/(? e + @failures[key] = [@count, e] + end + end + end + def message i = 0 total = @count.to_s fmt = "%#{total.size}d" @failures.map {|k, (n, v)| - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.gsub(/^/, ' | ')}" + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.b.gsub(/^/, ' | ')}" }.join("\n") end @@ -810,12 +897,21 @@ def pass? end end - def all_assertions(msg = nil) + def assert_all_assertions(msg = nil) all = AllFailures.new yield all ensure - assert(all.pass?, message(msg) {all.message}) + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def assert_all_assertions_foreach(msg = nil, *keys, &block) + all = AllFailures.new + all.foreach(*keys, &block) + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) end + alias all_assertions_foreach assert_all_assertions_foreach def build_message(head, template=nil, *arguments) #:nodoc: template &&= template.chomp From 95d27914258f00a014633b1d34c390acb9447b08 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 27 Dec 2020 21:27:24 +0900 Subject: [PATCH 005/144] CI: Add Ruby 3.0 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad0328be..e6698250 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,7 @@ jobs: - macos-10.15 - windows-latest ruby: + - 3.0 - 2.7 - 2.6 - 2.5 @@ -27,7 +28,7 @@ jobs: - { os: windows-latest , ruby: mingw } - { os: windows-latest , ruby: mswin } exclude: - - { os: windows-latest , ruby: 3.0.0-preview2 } + - { os: windows-latest , ruby: 3.0 } - { os: windows-latest , ruby: debug } steps: From d40d7edf9adc6d6797e399321209f6e18b1dac3a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:12:36 +0900 Subject: [PATCH 006/144] Bump version --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 7e5388d8..eec0bd0b 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.0' +bigdecimal_version = '3.0.1' Gem::Specification.new do |s| s.name = "bigdecimal" From 3e33a48015f399a2de8f65ec5f595a45e632b09f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:14:37 +0900 Subject: [PATCH 007/144] Bump version to 3.0.1-dev --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index eec0bd0b..425faaff 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1' +bigdecimal_version = '3.0.1.dev' Gem::Specification.new do |s| s.name = "bigdecimal" From a1d7cec0d1d6aaec9b90f09680aee5af3987d153 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:25 +0900 Subject: [PATCH 008/144] Use benchmark_driver --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 425faaff..15d4e146 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -32,6 +32,7 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") + s.add_development_dependency "benchmark_driver" s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" From 731b00fbd6f8c598e2b3386636f12dce167d04cd Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:39 +0900 Subject: [PATCH 009/144] Add benchmark task --- Rakefile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Rakefile b/Rakefile index 671e6bb6..f4a67197 100644 --- a/Rakefile +++ b/Rakefile @@ -16,3 +16,28 @@ end task travis: :test task test: :compile + +benchmark_tasks = [] +namespace :benchmark do + Dir.glob("benchmark/*.yml") do |benchmark| + name = File.basename(benchmark, ".*") + env = { + "RUBYLIB" => nil, + "BUNDLER_ORIG_RUBYLIB" => nil, + } + command_line = [ + RbConfig.ruby, "-v", "-S", "benchmark-driver", File.expand_path(benchmark) + ] + + desc "Run #{name} benchmark" + task name do + puts("```") + sh(env, *command_line) + puts("```") + end + benchmark_tasks << "benchmark:#{name}" + end +end + +desc "Run all benchmarks" +task benchmark: benchmark_tasks From e26995eff1310e7177970ce792f24898e20fe3a2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:27:59 +0900 Subject: [PATCH 010/144] Add simple benchmark of creation from integers --- benchmark/from_integer.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 benchmark/from_integer.yml diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml new file mode 100644 index 00000000..0a7f173b --- /dev/null +++ b/benchmark/from_integer.yml @@ -0,0 +1,15 @@ +loop_count: 100 + +contexts: +- gems: + bigdecimal: 3.0.1.dev +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + big_1e100 = 10**100 + +benchmark: + big_1e100: BigDecimal(big_1e100) From 751fd89e64cac00129a7d1c0e3f90824914ef452 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 15:29:46 +0900 Subject: [PATCH 011/144] Add benchmark job --- .github/workflows/benchmark.yml | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..7ae02d23 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,43 @@ +name: Benchmarking + +on: +- push +- pull_request + +jobs: + host: + name: ${{ matrix.os }} ${{ matrix.ruby }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-10.15 + - windows-latest + ruby: + - 3.0 + - 2.7 + - head + include: + - { os: windows-latest , ruby: mingw } + - { os: windows-latest , ruby: mswin } + exclude: + - { os: windows-latest , ruby: 3.0 } + - { os: windows-latest , ruby: debug } + + steps: + - uses: actions/checkout@v2 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: Install dependencies + run: | + bundle install + gem install bigdecimal -v 3.0.0 + rake install + + - run: rake benchmark From 741fb3e00f8777a244e3ace79f52065c81a0d385 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 20:26:28 +0900 Subject: [PATCH 012/144] Reduce conditional branch count in VpNewVarArg --- ext/bigdecimal/bigdecimal.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7fdf4012..1e6de0e2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2753,20 +2753,16 @@ VpNewVarArg(int argc, VALUE *argv) mf = (size_t)n; } - if (SPECIAL_CONST_P(iniValue)) { - switch (iniValue) { - case Qnil: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert nil into BigDecimal"); - case Qtrue: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert true into BigDecimal"); - case Qfalse: - if (!exc) return NULL; - rb_raise(rb_eTypeError, "can't convert false into BigDecimal"); - default: - break; - } + switch (iniValue) { + case Qnil: + case Qtrue: + case Qfalse: + if (!exc) return NULL; + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" into BigDecimal", iniValue); + + default: + break; } retry: From 905d0345ec35dbb717beaf5bc1dcebde614bde58 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 21:25:20 +0900 Subject: [PATCH 013/144] [Doc] Fix the document of BigDecimal() [ci-skip] --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1e6de0e2..e544bc7d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2830,7 +2830,7 @@ VpNewVarArg(int argc, VALUE *argv) } /* call-seq: - * BigDecimal(initial, digits, exception: true) + * BigDecimal(initial, digits=0, exception: true) * * Create a new BigDecimal object. * From 5c808eeabb092be7074805255b044fce337c439d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 28 Dec 2020 22:44:50 +0900 Subject: [PATCH 014/144] Reduce needless object allocation in f_BigDecimal --- ext/bigdecimal/bigdecimal.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e544bc7d..f89a66ed 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2870,19 +2870,17 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) { ENTER(1); Real *pv; - VALUE obj; if (argc > 0 && CLASS_OF(argv[0]) == rb_cBigDecimal) { if (argc == 1 || (argc == 2 && RB_TYPE_P(argv[1], T_HASH))) return argv[0]; } - obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); pv = VpNewVarArg(argc, argv); if (pv == NULL) return Qnil; SAVE(pv); if (ToValue(pv)) pv = VpCopy(NULL, pv); - RTYPEDDATA_DATA(obj) = pv; - RB_OBJ_FREEZE(obj); - return pv->obj = obj; + pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); + RB_OBJ_FREEZE(pv->obj); + return pv->obj; } static VALUE From 7504871c480c5236d390812017d7c282b646bb94 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 03:36:31 +0900 Subject: [PATCH 015/144] Refactor to decompose VpNewVarArg into small functions --- ext/bigdecimal/bigdecimal.c | 232 +++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 95 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f89a66ed..a7b099b1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2725,108 +2725,138 @@ opts_exception_p(VALUE opts) } #endif -static Real * -VpNewVarArg(int argc, VALUE *argv) +static VALUE +check_exception(VALUE bd) { - size_t mf; - VALUE opts = Qnil; - VALUE nFig; - VALUE iniValue; - double d; - int exc; + assert(is_kind_of_BigDecimal(bd)); + + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + ToValue(vp); /* ToValue performs exception check */ + + return bd; +} + +static VALUE +rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + Real *vp = GetVpValue(val, 1); + return check_exception(vp->obj); +} - argc = rb_scan_args(argc, argv, "11:", &iniValue, &nFig, &opts); - exc = opts_exception_p(opts); +static VALUE +rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + double d = RFLOAT_VALUE(val); + if (!isfinite(d)) { + Real *vp = VpCreateRbObject(1, NULL); /* vp->obj is allocated */ + VpDtoV(vp, d); + return check_exception(vp->obj); + } - if (argc == 1) { - mf = 0; + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); } - else { - /* expand GetPrecisionInt for exception suppression */ - ssize_t n = NUM2INT(nFig); - if (n < 0) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, "negative precision"); - } - mf = (size_t)n; + else if (digs > DBLE_FIG) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, "precision too large."); } - switch (iniValue) { + Real *vp = GetVpValueWithPrec(val, digs, 1); + return check_exception(vp->obj); +} + +static VALUE +rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) { + if (!raise_exception) + return Qnil; + rb_raise(rb_eArgError, + "can't omit precision for a %"PRIsVALUE".", + CLASS_OF(val)); + } + Real *vp = GetVpValueWithPrec(val, digs, 1); + return check_exception(vp->obj); +} + +static VALUE +rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + if (digs == SIZE_MAX) + digs = 0; + + const char *c_str = StringValueCStr(val); + Real *vp = VpAlloc(digs, c_str, 1, raise_exception); + if (!vp) + return Qnil; + vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RB_OBJ_FREEZE(vp->obj); + return check_exception(vp->obj); +} + +static VALUE +rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + switch (val) { case Qnil: case Qtrue: case Qfalse: - if (!exc) return NULL; + if (!raise_exception) + return Qnil; rb_raise(rb_eTypeError, - "can't convert %"PRIsVALUE" into BigDecimal", iniValue); + "can't convert %"PRIsVALUE" into BigDecimal", val); default: break; } - retry: - switch (TYPE(iniValue)) { - case T_DATA: - if (is_kind_of_BigDecimal(iniValue)) { - return DATA_PTR(iniValue); - } - break; - - case T_FIXNUM: - /* fall through */ - case T_BIGNUM: - return GetVpValue(iniValue, 1); - - case T_FLOAT: - d = RFLOAT_VALUE(iniValue); - if (!isfinite(d)) { - Real *pv = VpCreateRbObject(1, NULL); - VpDtoV(pv, d); - return pv; - } - if (mf > DBLE_FIG) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, "precision too large."); - } - /* fall through */ - case T_RATIONAL: - if (NIL_P(nFig)) { - if (!exc) { - return NULL; - } - rb_raise(rb_eArgError, - "can't omit precision for a %"PRIsVALUE".", - RB_OBJ_CLASSNAME(iniValue)); - } - return GetVpValueWithPrec(iniValue, mf, 1); + if (is_kind_of_BigDecimal(val)) { + if (digs == SIZE_MAX) + return check_exception(val); - case T_COMPLEX: - { - VALUE im; - im = rb_complex_imag(iniValue); - if (!is_zero(im)) { - rb_raise(rb_eArgError, - "Unable to make a BigDecimal from non-zero imaginary number"); - } - iniValue = rb_complex_real(iniValue); - goto retry; + Real *vp; + TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); + vp = VpCopy(NULL, vp); + vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RB_OBJ_FREEZE(vp->obj); + return check_exception(vp->obj); + } + else if (RB_INTEGER_TYPE_P(val)) { + return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_FLOAT_TYPE_P(val)) { + return rb_float_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_RATIONAL)) { + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + } + else if (RB_TYPE_P(val, T_COMPLEX)) { + VALUE im = rb_complex_imag(val); + if (!is_zero(im)) { + /* TODO: handle raise_exception */ + rb_raise(rb_eArgError, + "Unable to make a BigDecimal from non-zero imaginary number"); } - - case T_STRING: - /* fall through */ - default: - break; + return rb_convert_to_BigDecimal(rb_complex_real(val), digs, raise_exception); } - /* TODO: support to_d */ - if (!exc) { - iniValue = rb_check_convert_type(iniValue, T_STRING, "String", "to_str"); - if (NIL_P(iniValue)) return NULL; + else if (RB_TYPE_P(val, T_STRING)) { + return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } - StringValueCStr(iniValue); - return VpAlloc(mf, RSTRING_PTR(iniValue), 1, exc); + /* TODO: chheck to_d */ + /* TODO: chheck to_int */ + if (!raise_exception) { + VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); + if (NIL_P(str)) + return Qnil; + val = str; + } + return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } /* call-seq: @@ -2868,19 +2898,31 @@ VpNewVarArg(int argc, VALUE *argv) static VALUE f_BigDecimal(int argc, VALUE *argv, VALUE self) { - ENTER(1); - Real *pv; - - if (argc > 0 && CLASS_OF(argv[0]) == rb_cBigDecimal) { - if (argc == 1 || (argc == 2 && RB_TYPE_P(argv[1], T_HASH))) return argv[0]; + VALUE val, digs_v, opts = Qnil; + argc = rb_scan_args(argc, argv, "11:", &val, &digs_v, &opts); + int exception = opts_exception_p(opts); + + size_t digs = SIZE_MAX; /* this means digs is omitted */ + if (argc > 1) { + digs_v = rb_to_int(digs_v); + if (FIXNUM_P(digs_v)) { + long n = FIX2LONG(digs_v); + if (n < 0) + goto negative_digs; + digs = (size_t)n; + } + else { + if (RBIGNUM_NEGATIVE_P(digs_v)) { + negative_digs: + if (!exception) + return Qnil; + rb_raise(rb_eArgError, "negative precision"); + } + digs = NUM2SIZET(digs_v); + } } - pv = VpNewVarArg(argc, argv); - if (pv == NULL) return Qnil; - SAVE(pv); - if (ToValue(pv)) pv = VpCopy(NULL, pv); - pv->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, pv); - RB_OBJ_FREEZE(pv->obj); - return pv->obj; + + return rb_convert_to_BigDecimal(val, digs, exception); } static VALUE From 48583417907f2b9c4ded10e4376498c6e4215884 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 12:46:37 +0900 Subject: [PATCH 016/144] CI: Stop using macos-11.0 due to the unstability --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6698250..106b8040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,6 @@ jobs: os: - ubuntu-20.04 - ubuntu-18.04 - - macos-11.0 - macos-10.15 - windows-latest ruby: From 205a8a618341fdd117f89fa821487d78fce0d184 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 13:12:48 +0900 Subject: [PATCH 017/144] Benchmark: Use bigdecimal 3.0.0 for the baseline --- benchmark/from_integer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index 0a7f173b..e0896f33 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -2,7 +2,7 @@ loop_count: 100 contexts: - gems: - bigdecimal: 3.0.1.dev + bigdecimal: 3.0.0 - name: "master" prelude: |- $LOAD_PATH.unshift(File.expand_path("lib")) From d109c9b0e6d918a8e5338e295b7cd9a014bd9af4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 13:31:51 +0900 Subject: [PATCH 018/144] Benchmark: Use larger number --- benchmark/from_integer.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index e0896f33..f98c5722 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -9,7 +9,9 @@ contexts: require "bigdecimal" prelude: |- - big_1e100 = 10**100 + figs = (0..9).to_a + + big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_1e100: BigDecimal(big_1e100) + big_n10000: BigDecimal(big_n10000) From 6fd171308ba0c046a93b5a5b37be2099ab0b3e8e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 16:34:23 +0900 Subject: [PATCH 019/144] Refactor to extract VpCheckException --- ext/bigdecimal/bigdecimal.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7b099b1..f659359c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -208,21 +208,29 @@ is_kind_of_BigDecimal(VALUE const v) return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); } -static VALUE -ToValue(Real *p) +static void +VpCheckException(Real *p, bool always) { if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", 0); + VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", always); } else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 0); + VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", always); } else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 0); + VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", always); } +} + +static VALUE +VpCheckGetValue(Real *p) +{ + VpCheckException(p, false); return p->obj; } +#define ToValue(p) VpCheckGetValue(p) + NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -838,15 +846,7 @@ BigDecimal_IsFinite(VALUE self) static void BigDecimal_check_num(Real *p) { - if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", 1); - } - else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", 1); - } - else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", 1); - } + VpCheckException(p, true); } static VALUE BigDecimal_split(VALUE self); From 2c5a288caf33ee7309186765d988d2990ca8191f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 16:35:25 +0900 Subject: [PATCH 020/144] Alloc wrapper object before VpAlloc Calling TypedData_Wrap_Struct after VpAlloc may cause memory leak. This commit reverts 5c808eeabb092be7074805255b044fce337c439d. --- ext/bigdecimal/bigdecimal.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f659359c..89653f49 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2792,12 +2792,14 @@ rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) digs = 0; const char *c_str = StringValueCStr(val); + VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); Real *vp = VpAlloc(digs, c_str, 1, raise_exception); if (!vp) return Qnil; - vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); - RB_OBJ_FREEZE(vp->obj); - return check_exception(vp->obj); + RTYPEDDATA_DATA(obj) = vp; + vp->obj = obj; + RB_OBJ_FREEZE(obj); + return VpCheckGetValue(vp); } static VALUE @@ -2822,10 +2824,13 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) Real *vp; TypedData_Get_Struct(val, Real, &BigDecimal_data_type, vp); + + VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); - vp->obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, vp); + RTYPEDDATA_DATA(copy) = vp; + vp->obj = copy; RB_OBJ_FREEZE(vp->obj); - return check_exception(vp->obj); + return VpCheckGetValue(vp); } else if (RB_INTEGER_TYPE_P(val)) { return rb_inum_convert_to_BigDecimal(val, digs, raise_exception); @@ -2929,14 +2934,16 @@ static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { ENTER(1); - char const *c_str; - Real *pv; - c_str = StringValueCStr(str); + char const *c_str = StringValueCStr(str); + VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); + + Real *pv; GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - pv->obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, pv); + RTYPEDDATA_DATA(obj) = pv; + pv->obj = obj; RB_OBJ_FREEZE(pv->obj); - return pv->obj; + return obj; } /* call-seq: From a6d3bd2d442c8051e5de12d373c674527b52c612 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 17:14:36 +0900 Subject: [PATCH 021/144] Define bool, true, and false for old Ruby --- ext/bigdecimal/bigdecimal.h | 14 ++++++++++++++ ext/bigdecimal/extconf.rb | 2 ++ 2 files changed, 16 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 28f3363b..c89f212a 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -14,6 +14,20 @@ #include "ruby/ruby.h" #include +#if defined(__bool_true_false_are_defined) +# /* Take that. */ + +#elif defined(HAVE_STDBOOL_H) +# include + +#else +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)-1) +# define __bool_true_false_are_defined +#endif + #ifndef RB_UNUSED_VAR # ifdef __GNUC__ # define RB_UNUSED_VAR(x) x __attribute__ ((unused)) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 63123e28..41b8df8d 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -28,6 +28,8 @@ def check_bigdecimal_version(gemspec_path) check_bigdecimal_version(gemspec_path) +have_header("stdbool.h") + have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") have_func("finite", "math.h") From 507f0a6a64a3f43b679553be6ae8f7b5c7c1fd40 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 29 Dec 2020 22:36:51 +0900 Subject: [PATCH 022/144] Remove needless pointer checks xmalloc and xrealloc return non-NULL pointers or raise memory error. --- ext/bigdecimal/bigdecimal.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 89653f49..65f412e6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -3779,9 +3779,6 @@ VP_EXPORT void * VpMemAlloc(size_t mb) { void *p = xmalloc(mb); - if (!p) { - VpException(VP_EXCEPTION_MEMORY, "failed to allocate memory", 1); - } memset(p, 0, mb); #ifdef BIGDECIMAL_DEBUG gnAlloc++; /* Count allocation call */ @@ -3792,11 +3789,7 @@ VpMemAlloc(size_t mb) VP_EXPORT void * VpMemRealloc(void *ptr, size_t mb) { - void *p = xrealloc(ptr, mb); - if (!p) { - VpException(VP_EXCEPTION_MEMORY, "failed to allocate memory", 1); - } - return p; + return xrealloc(ptr, mb); } VP_EXPORT void @@ -4348,7 +4341,6 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) /* at least mx digits. */ /* szVal==NULL ==> allocate zero value. */ vp = VpAllocReal(mx); - /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, 1); /* initialize vp to zero. */ return vp; @@ -4524,7 +4516,6 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) nalloc = Max(nalloc, mx); mx = nalloc; vp = VpAllocReal(mx); - /* xmalloc() always returns(or throw interruption) */ vp->MaxPrec = mx; /* set max precision */ VpSetZero(vp, sign); VpCtoV(vp, psz, ni, psz + ipf, nf, psz + ipe, ne); From 7d463f802b0387c04f92ae00d255f059726f784f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 30 Dec 2020 00:19:10 +0900 Subject: [PATCH 023/144] Remove VP_EXCEPTION_MEMORY It is no longer used due to the previous commit. --- ext/bigdecimal/bigdecimal.c | 3 +-- ext/bigdecimal/bigdecimal.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 65f412e6..888b21f2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -4007,7 +4007,7 @@ VpException(unsigned short f, const char *str,int always) { unsigned short const exception_mode = VpGetException(); - if (f == VP_EXCEPTION_OP || f == VP_EXCEPTION_MEMORY) always = 1; + if (f == VP_EXCEPTION_OP) always = 1; if (always || (exception_mode & f)) { switch(f) { @@ -4019,7 +4019,6 @@ VpException(unsigned short f, const char *str,int always) case VP_EXCEPTION_OP: rb_raise(rb_eFloatDomainError, "%s", str); break; - case VP_EXCEPTION_MEMORY: default: rb_fatal("%s", str); } diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index c89f212a..80278738 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -221,7 +221,6 @@ extern VALUE rb_cBigDecimal; /* Following 2 exceptions can't controlled by user */ #define VP_EXCEPTION_OP ((unsigned short)0x0020) -#define VP_EXCEPTION_MEMORY ((unsigned short)0x0040) #define RMPD_EXCEPTION_MODE_DEFAULT 0U From 97e9feeebd4f9576b4522d0a6a05dcd7d48c51b2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 31 Dec 2020 01:43:08 +0900 Subject: [PATCH 024/144] Remove ToValue --- ext/bigdecimal/bigdecimal.c | 120 ++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 888b21f2..13b2cdcf 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -229,8 +229,6 @@ VpCheckGetValue(Real *p) return p->obj; } -#define ToValue(p) VpCheckGetValue(p) - NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -289,7 +287,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) pv = GetVpValueWithPrec(num, -1, must); if (pv == NULL) goto SomeOneMayDoIt; - v = BigDecimal_div2(ToValue(pv), rb_rational_den(v), LONG2NUM(prec)); + v = BigDecimal_div2(VpCheckGetValue(pv), rb_rational_den(v), LONG2NUM(prec)); goto again; } @@ -566,7 +564,7 @@ BigDecimal_load(VALUE self, VALUE str) if (m && pv->MaxPrec > m) { pv->MaxPrec = m+1; } - return ToValue(pv); + return VpCheckGetValue(pv); } static unsigned short @@ -1004,7 +1002,7 @@ BigDecimal_coerce(VALUE self, VALUE other) if (RB_TYPE_P(other, T_FLOAT)) { GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); - obj = rb_assoc_new(ToValue(b), self); + obj = rb_assoc_new(VpCheckGetValue(b), self); } else { if (RB_TYPE_P(other, T_RATIONAL)) { @@ -1090,7 +1088,7 @@ BigDecimal_add(VALUE self, VALUE r) VpAddSub(c, a, b, 1); } } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1148,7 +1146,7 @@ BigDecimal_sub(VALUE self, VALUE r) VpAddSub(c, a, b, -1); } } - return ToValue(c); + return VpCheckGetValue(c); } static VALUE @@ -1355,7 +1353,7 @@ BigDecimal_neg(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0")); VpAsgn(c, a, -1); - return ToValue(c); + return VpCheckGetValue(c); } /* @@ -1397,7 +1395,7 @@ BigDecimal_mult(VALUE self, VALUE r) mx = a->Prec + b->Prec; GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); VpMult(c, a, b); - return ToValue(c); + return VpCheckGetValue(c); } static VALUE @@ -1459,7 +1457,7 @@ BigDecimal_div(VALUE self, VALUE r) if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ VpInternalRound(c, 0, c->frac[c->Prec-1], (BDIGIT)(VpBaseVal() * (BDIGIT_DBL)res->frac[0] / div->frac[0])); } - return ToValue(c); + return VpCheckGetValue(c); } /* @@ -1562,7 +1560,7 @@ BigDecimal_mod(VALUE self, VALUE r) /* %: a%b = a - (a.to_f/b).floor * b */ if (BigDecimal_DoDivmod(self, r, &div, &mod)) { SAVE(div); SAVE(mod); - return ToValue(mod); + return VpCheckGetValue(mod); } return DoSomeOne(self, r, '%'); } @@ -1627,7 +1625,7 @@ BigDecimal_remainder(VALUE self, VALUE r) /* remainder */ Real *d, *rv = 0; f = BigDecimal_divremain(self, r, &d, &rv); if (!NIL_P(f)) return f; - return ToValue(rv); + return VpCheckGetValue(rv); } /* call-seq: @@ -1660,7 +1658,7 @@ BigDecimal_divmod(VALUE self, VALUE r) if (BigDecimal_DoDivmod(self, r, &div, &mod)) { SAVE(div); SAVE(mod); - return rb_assoc_new(ToValue(div), ToValue(mod)); + return rb_assoc_new(VpCheckGetValue(div), VpCheckGetValue(mod)); } return DoSomeOne(self,r,rb_intern("divmod")); } @@ -1678,7 +1676,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) Real *div = NULL; Real *mod; if (BigDecimal_DoDivmod(self, b, &div, &mod)) { - return BigDecimal_to_i(ToValue(div)); + return BigDecimal_to_i(VpCheckGetValue(div)); } return DoSomeOne(self, b, rb_intern("div")); } @@ -1703,7 +1701,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1761,7 +1759,7 @@ BigDecimal_add2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1791,7 +1789,7 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1809,7 +1807,7 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) VpSetPrecLimit(pl); GUARD_OBJ(cv, GetVpValue(c, 1)); VpLeftRound(cv, VpGetRoundMode(), mx); - return ToValue(cv); + return VpCheckGetValue(cv); } } @@ -1835,7 +1833,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpAsgn(c, a, 1); VpChangeSign(c, 1); - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1859,7 +1857,7 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) if (mx <= n) mx = n; GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpSqrt(c, a); - return ToValue(c); + return VpCheckGetValue(c); } /* Return the integer part of the number, as a BigDecimal. @@ -1875,7 +1873,7 @@ BigDecimal_fix(VALUE self) mx = a->Prec *(VpBaseFig() + 1); GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1950,9 +1948,9 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -1996,9 +1994,9 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* Return the fractional part of the number, as a BigDecimal. @@ -2014,7 +2012,7 @@ BigDecimal_frac(VALUE self) mx = a->Prec * (VpBaseFig() + 1); GUARD_OBJ(c, VpCreateRbObject(mx, "0")); VpFrac(c, a); - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2059,9 +2057,9 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) VPrint(stderr, "floor: c=%\n", c); #endif if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2102,9 +2100,9 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { - return BigDecimal_to_i(ToValue(c)); + return BigDecimal_to_i(VpCheckGetValue(c)); } - return ToValue(c); + return VpCheckGetValue(c); } /* call-seq: @@ -2406,7 +2404,7 @@ rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2447,7 +2445,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) y = VpCreateRbObject(n, "0"); RB_GC_GUARD(y->obj); VpSetNaN(y); - return ToValue(y); + return VpCheckGetValue(y); } retry: @@ -2538,18 +2536,18 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) /* (+0) ** (-num) -> Infinity */ VpSetPosInf(y); } - return ToValue(y); + return VpCheckGetValue(y); } else if (is_zero(vexp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } if (is_zero(vexp)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else if (is_one(vexp)) { return self; @@ -2561,20 +2559,20 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (is_integer(vexp)) { if (is_even(vexp)) { /* (-Infinity) ** (-even_integer) -> +0 */ - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } else { @@ -2597,7 +2595,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetPosInf(y); } - return ToValue(y); + return VpCheckGetValue(y); } } @@ -2607,7 +2605,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return ToValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1")); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { @@ -2618,13 +2616,13 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetInf(y, -VpGetSign(x)); } - return ToValue(y); + return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } else { @@ -2636,13 +2634,13 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { VpSetInf(y, -VpGetSign(x)); } - return ToValue(y); + return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return ToValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0")); } else { - return ToValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0")); } } } @@ -2663,7 +2661,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (!NIL_P(prec) && VpIsDef(y)) { VpMidRound(y, VpGetRoundMode(), n); } - return ToValue(y); + return VpCheckGetValue(y); } /* call-seq: @@ -2732,7 +2730,7 @@ check_exception(VALUE bd) Real *vp; TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); - ToValue(vp); /* ToValue performs exception check */ + VpCheckGetValue(vp); /* VpCheckGetValue performs exception check */ return bd; } @@ -3140,14 +3138,14 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } if (infinite) { if (negative) { - return ToValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); + return VpCheckGetValue(GetVpValueWithPrec(INT2FIX(0), prec, 1)); } else { Real* vy; vy = VpCreateRbObject(prec, "#0"); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); - return ToValue(vy); + return VpCheckGetValue(vy); } } else if (nan) { @@ -3155,7 +3153,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) vy = VpCreateRbObject(prec, "#0"); VpSetNaN(vy); RB_GC_GUARD(vy->obj); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (vx == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); @@ -3172,7 +3170,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = ToValue(VpCreateRbObject(1, "1")); + one = VpCheckGetValue(VpCreateRbObject(1, "1")); y = one; d = y; i = 1; @@ -3303,14 +3301,14 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) vy = VpCreateRbObject(prec, "#0"); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (nan) { Real* vy; vy = VpCreateRbObject(prec, "#0"); RB_GC_GUARD(vy->obj); VpSetNaN(vy); - return ToValue(vy); + return VpCheckGetValue(vy); } else if (zero || negative) { rb_raise(rb_eMathDomainError, @@ -3319,10 +3317,10 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) else if (vx == NULL) { cannot_be_coerced_into_BigDecimal(rb_eArgError, x); } - x = ToValue(vx); + x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = ToValue(VpCreateRbObject(1, "1")); - RB_GC_GUARD(two) = ToValue(VpCreateRbObject(1, "2")); + RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1")); + RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2")); n = prec + rmpd_double_figures(); RB_GC_GUARD(vn) = SSIZET2NUM(n); @@ -3330,7 +3328,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, ToValue(VpCreateRbObject(1, buf)), vn); + x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf)), vn); } else { expo = 0; @@ -3362,7 +3360,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo != 0) { VALUE log10, vexpo, dy; log10 = BigMath_s_log(klass, INT2FIX(10), vprec); - vexpo = ToValue(GetVpValue(SSIZET2NUM(expo), 1)); + vexpo = VpCheckGetValue(GetVpValue(SSIZET2NUM(expo), 1)); dy = BigDecimal_mult(log10, vexpo); y = BigDecimal_add(y, dy); } From 271cebe56738c32ae2ab4f7aa3c0f9d7b6503bec Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 31 Dec 2020 02:01:35 +0900 Subject: [PATCH 025/144] Refactor object allocation --- ext/bigdecimal/bigdecimal.c | 203 +++++++++++++++++++----------------- ext/bigdecimal/bigdecimal.h | 5 +- 2 files changed, 107 insertions(+), 101 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 13b2cdcf..96fe253d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -266,7 +266,7 @@ GetVpValueWithPrec(VALUE v, long prec, int must) if (prec > (long)DBLE_FIG) goto SomeOneMayDoIt; d = RFLOAT_VALUE(v); if (!isfinite(d)) { - pv = VpCreateRbObject(1, NULL); + pv = VpCreateRbObject(1, NULL, true); VpDtoV(pv, d); return pv; } @@ -275,9 +275,9 @@ GetVpValueWithPrec(VALUE v, long prec, int must) goto again; } if (1/d < 0.0) { - return VpCreateRbObject(prec, "-0"); + return VpCreateRbObject(prec, "-0", true); } - return VpCreateRbObject(prec, "0"); + return VpCreateRbObject(prec, "0", true); case T_RATIONAL: if (prec < 0) goto unable_to_coerce_without_prec; @@ -306,20 +306,20 @@ GetVpValueWithPrec(VALUE v, long prec, int must) case T_FIXNUM: sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD); + return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); #ifdef ENABLE_NUMERIC_STRING case T_STRING: StringValueCStr(v); - return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, - RSTRING_PTR(v)); + return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, + RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ case T_BIGNUM: bg = rb_big2str(v, 10); PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg)); + return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } @@ -559,7 +559,7 @@ BigDecimal_load(VALUE self, VALUE str) m = m*10 + (unsigned long)(ch-'0'); } if (m > VpBaseFig()) m -= VpBaseFig(); - GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self)); + GUARD_OBJ(pv, VpNewRbClass(m, (char *)pch, self, true, true)); m /= VpBaseFig(); if (m && pv->MaxPrec > m) { pv->MaxPrec = m+1; @@ -774,21 +774,39 @@ GetPrecisionInt(VALUE v) return n; } +static VALUE +BigDecimal_wrap_struct(VALUE obj, Real *vp) +{ + assert(is_kind_of_BigDecimal(obj)); + assert(vp != NULL); + + if (vp->obj == obj && RTYPEDDATA_DATA(obj) == vp) + return obj; + + assert(RTYPEDDATA_DATA(obj) == NULL); + assert(vp->obj == 0); + + RTYPEDDATA_DATA(obj) = vp; + vp->obj = obj; + RB_OBJ_FREEZE(obj); + return obj; +} + VP_EXPORT Real * -VpNewRbClass(size_t mx, const char *str, VALUE klass) +VpNewRbClass(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception) { VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - Real *pv = VpAlloc(mx, str, 1, 1); - RTYPEDDATA_DATA(obj) = pv; - pv->obj = obj; - RB_OBJ_FREEZE(obj); + Real *pv = VpAlloc(mx, str, strict_p, raise_exception); + if (!pv) + return NULL; + BigDecimal_wrap_struct(obj, pv); return pv; } VP_EXPORT Real * -VpCreateRbObject(size_t mx, const char *str) +VpCreateRbObject(size_t mx, const char *str, bool raise_exception) { - return VpNewRbClass(mx, str, rb_cBigDecimal); + return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } #define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) @@ -1076,11 +1094,11 @@ BigDecimal_add(VALUE self, VALUE r) mx = GetAddSubPrec(a, b); if (mx == (size_t)-1L) { - GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0")); + GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); VpAddSub(c, a, b, 1); } else { - GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(mx * (VpBaseFig() + 1), "0", true)); if(!mx) { VpSetInf(c, VpGetSign(a)); } @@ -1134,11 +1152,11 @@ BigDecimal_sub(VALUE self, VALUE r) mx = GetAddSubPrec(a,b); if (mx == (size_t)-1L) { - GUARD_OBJ(c,VpCreateRbObject(VpBaseFig() + 1, "0")); + GUARD_OBJ(c, VpCreateRbObject(VpBaseFig() + 1, "0", true)); VpAddSub(c, a, b, -1); } else { - GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c,VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); if (!mx) { VpSetInf(c,VpGetSign(a)); } @@ -1351,7 +1369,7 @@ BigDecimal_neg(VALUE self) ENTER(5); Real *c, *a; GUARD_OBJ(a, GetVpValue(self, 1)); - GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(a->Prec *(VpBaseFig() + 1), "0", true)); VpAsgn(c, a, -1); return VpCheckGetValue(c); } @@ -1393,7 +1411,7 @@ BigDecimal_mult(VALUE self, VALUE r) SAVE(b); mx = a->Prec + b->Prec; - GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0")); + GUARD_OBJ(c, VpCreateRbObject(mx *(VpBaseFig() + 1), "0", true)); VpMult(c, a, b); return VpCheckGetValue(c); } @@ -1426,8 +1444,8 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) mx++; /* NOTE: An additional digit is needed for the compatibility to the version 1.2.1 and the former. */ mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0")); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } @@ -1492,22 +1510,22 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) rb_raise(rb_eZeroDivError, "divided by 0"); } if (VpIsInf(a)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); VpSetInf(d, (SIGNED_VALUE)(VpGetSign(a) == VpGetSign(b) ? 1 : -1)); - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); + GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); *div = d; *mod = c; return Qtrue; } if (VpIsInf(b)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); *div = d; *mod = a; return Qtrue; } if (VpIsZero(a)) { - GUARD_OBJ(c, VpCreateRbObject(1, "0")); - GUARD_OBJ(d, VpCreateRbObject(1, "0")); + GUARD_OBJ(c, VpCreateRbObject(1, "0", true)); + GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); *div = d; *mod = c; return Qtrue; @@ -1516,17 +1534,17 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) mx = a->Prec + vabs(a->exponent); if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); mx = c->Prec * (VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0")); + GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); VpMult(res, d, b); VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0")); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; @@ -1537,8 +1555,8 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; NaN: - GUARD_OBJ(c, VpCreateRbObject(1, "NaN")); - GUARD_OBJ(d, VpCreateRbObject(1, "NaN")); + GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); + GUARD_OBJ(d, VpCreateRbObject(1, "NaN", true)); *div = d; *mod = c; return Qtrue; @@ -1588,17 +1606,17 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) SAVE(b); mx = (a->MaxPrec + b->MaxPrec) *VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); - GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); - GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(rr, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); + GUARD_OBJ(ff, VpCreateRbObject((mx+1) * 2 + (VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); mx = c->Prec *(VpBaseFig() + 1); - GUARD_OBJ(d, VpCreateRbObject(mx, "0")); - GUARD_OBJ(f, VpCreateRbObject(mx, "0")); + GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(f, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); /* 0: round off */ @@ -1692,12 +1710,12 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) size_t mx = ix + VpBaseFig()*2; size_t pl = VpSetPrecLimit(0); - GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0")); + GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); GUARD_OBJ(bv, GetVpValue(b, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; - GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0")); + GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); VpDivd(cv, res, av, bv); VpSetPrecLimit(pl); VpLeftRound(cv, VpGetRoundMode(), ix); @@ -1830,7 +1848,7 @@ BigDecimal_abs(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpAsgn(c, a, 1); VpChangeSign(c, 1); return VpCheckGetValue(c); @@ -1855,7 +1873,7 @@ BigDecimal_sqrt(VALUE self, VALUE nFig) n = GetPrecisionInt(nFig) + VpDblFig() + BASE_FIG; if (mx <= n) mx = n; - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSqrt(c, a); return VpCheckGetValue(c); } @@ -1871,7 +1889,7 @@ BigDecimal_fix(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec *(VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpActiveRound(c, a, VP_ROUND_DOWN, 0); /* 0: round off */ return VpCheckGetValue(c); } @@ -1944,7 +1962,7 @@ BigDecimal_round(int argc, VALUE *argv, VALUE self) pl = VpSetPrecLimit(0); GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, sw, iLoc); if (round_to_int) { @@ -1990,7 +2008,7 @@ BigDecimal_truncate(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_DOWN, iLoc); /* 0: truncate */ if (argc == 0) { @@ -2010,7 +2028,7 @@ BigDecimal_frac(VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpFrac(c, a); return VpCheckGetValue(c); } @@ -2050,7 +2068,7 @@ BigDecimal_floor(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_FLOOR, iLoc); #ifdef BIGDECIMAL_DEBUG @@ -2096,7 +2114,7 @@ BigDecimal_ceil(int argc, VALUE *argv, VALUE self) GUARD_OBJ(a, GetVpValue(self, 1)); mx = a->Prec * (VpBaseFig() + 1); - GUARD_OBJ(c, VpCreateRbObject(mx, "0")); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); VpSetPrecLimit(pl); VpActiveRound(c, a, VP_ROUND_CEIL, iLoc); if (argc == 0) { @@ -2404,7 +2422,7 @@ rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) volatile VALUE obj = exp->obj; if (VpIsZero(exp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } log_x = BigMath_log(x->obj, SSIZET2NUM(n+1)); @@ -2442,7 +2460,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) n = NIL_P(prec) ? (ssize_t)(x->Prec*VpBaseFig()) : NUM2SSIZET(prec); if (VpIsNaN(x)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); RB_GC_GUARD(y->obj); VpSetNaN(y); return VpCheckGetValue(y); @@ -2514,7 +2532,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (VpIsZero(x)) { if (is_negative(vexp)) { - y = VpCreateRbObject(n, "#0"); + y = VpCreateRbObject(n, "#0", true); RB_GC_GUARD(y->obj); if (BIGDECIMAL_NEGATIVE_P(x)) { if (is_integer(vexp)) { @@ -2539,15 +2557,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } if (is_zero(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else if (is_one(vexp)) { return self; @@ -2559,24 +2577,24 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (is_integer(vexp)) { if (is_even(vexp)) { /* (-Infinity) ** (-even_integer) -> +0 */ - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } else { /* (-Infinity) ** (-odd_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { /* (-Infinity) ** (-non_integer) -> -0 */ - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } else { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (BIGDECIMAL_NEGATIVE_P(x)) { if (is_integer(vexp)) { if (is_even(vexp)) { @@ -2605,11 +2623,11 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); if (is_one(abs_value)) { - return VpCheckGetValue(VpCreateRbObject(n, "1")); + return VpCheckGetValue(VpCreateRbObject(n, "1", true)); } else if (RTEST(rb_funcall(abs_value, '<', 1, INT2FIX(1)))) { if (is_negative(vexp)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (is_even(vexp)) { VpSetInf(y, VpGetSign(x)); } @@ -2619,15 +2637,15 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } else { if (is_positive(vexp)) { - y = VpCreateRbObject(n, "0"); + y = VpCreateRbObject(n, "0", true); if (is_even(vexp)) { VpSetInf(y, VpGetSign(x)); } @@ -2637,10 +2655,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } else if (BIGDECIMAL_NEGATIVE_P(x) && is_even(vexp)) { - return VpCheckGetValue(VpCreateRbObject(n, "-0")); + return VpCheckGetValue(VpCreateRbObject(n, "-0", true)); } else { - return VpCheckGetValue(VpCreateRbObject(n, "0")); + return VpCheckGetValue(VpCreateRbObject(n, "0", true)); } } } @@ -2652,10 +2670,10 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (VpIsDef(x)) { mp = x->Prec * (VpBaseFig() + 1); - GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0")); + GUARD_OBJ(y, VpCreateRbObject(mp * (ma + 1), "0", true)); } else { - GUARD_OBJ(y, VpCreateRbObject(1, "0")); + GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); } VpPower(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { @@ -2747,7 +2765,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { double d = RFLOAT_VALUE(val); if (!isfinite(d)) { - Real *vp = VpCreateRbObject(1, NULL); /* vp->obj is allocated */ + Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ VpDtoV(vp, d); return check_exception(vp->obj); } @@ -2790,13 +2808,9 @@ rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) digs = 0; const char *c_str = StringValueCStr(val); - VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); - Real *vp = VpAlloc(digs, c_str, 1, raise_exception); + Real *vp = VpCreateRbObject(digs, c_str, raise_exception); if (!vp) return Qnil; - RTYPEDDATA_DATA(obj) = vp; - vp->obj = obj; - RB_OBJ_FREEZE(obj); return VpCheckGetValue(vp); } @@ -2825,9 +2839,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); - RTYPEDDATA_DATA(copy) = vp; - vp->obj = copy; - RB_OBJ_FREEZE(vp->obj); + BigDecimal_wrap_struct(copy, vp); return VpCheckGetValue(vp); } else if (RB_INTEGER_TYPE_P(val)) { @@ -2931,17 +2943,12 @@ f_BigDecimal(int argc, VALUE *argv, VALUE self) static VALUE BigDecimal_s_interpret_loosely(VALUE klass, VALUE str) { - ENTER(1); - char const *c_str = StringValueCStr(str); - VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); - - Real *pv; - GUARD_OBJ(pv, VpAlloc(0, c_str, 0, 1)); - RTYPEDDATA_DATA(obj) = pv; - pv->obj = obj; - RB_OBJ_FREEZE(pv->obj); - return obj; + Real *vp = VpNewRbClass(0, c_str, klass, false, true); + if (!vp) + return Qnil; + else + return VpCheckGetValue(vp); } /* call-seq: @@ -3142,7 +3149,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } else { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); @@ -3150,7 +3157,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } else if (nan) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); VpSetNaN(vy); RB_GC_GUARD(vy->obj); return VpCheckGetValue(vy); @@ -3170,7 +3177,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) VpSetSign(vx, 1); } - one = VpCheckGetValue(VpCreateRbObject(1, "1")); + one = VpCheckGetValue(VpCreateRbObject(1, "1", true)); y = one; d = y; i = 1; @@ -3298,14 +3305,14 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } if (infinite && !negative) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); RB_GC_GUARD(vy->obj); VpSetInf(vy, VP_SIGN_POSITIVE_INFINITE); return VpCheckGetValue(vy); } else if (nan) { Real* vy; - vy = VpCreateRbObject(prec, "#0"); + vy = VpCreateRbObject(prec, "#0", true); RB_GC_GUARD(vy->obj); VpSetNaN(vy); return VpCheckGetValue(vy); @@ -3319,8 +3326,8 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) } x = VpCheckGetValue(vx); - RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1")); - RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2")); + RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); + RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); n = prec + rmpd_double_figures(); RB_GC_GUARD(vn) = SSIZET2NUM(n); @@ -3328,7 +3335,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (expo < 0 || expo >= 3) { char buf[DECIMAL_SIZE_OF_BITS(SIZEOF_VALUE * CHAR_BIT) + 4]; snprintf(buf, sizeof(buf), "1E%"PRIdVALUE, -expo); - x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf)), vn); + x = BigDecimal_mult2(x, VpCheckGetValue(VpCreateRbObject(1, buf, true)), vn); } else { expo = 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 80278738..900ebd41 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -285,10 +285,9 @@ typedef struct { * ------------------ */ -VP_EXPORT Real * -VpNewRbClass(size_t mx, char const *str, VALUE klass); +VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool strict_p, bool raise_exception); -VP_EXPORT Real *VpCreateRbObject(size_t mx,const char *str); +VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); static inline BDIGIT rmpd_base_value(void) { return RMPD_BASE; } From 1d30d5ad6d6c0485d2ccda27ecef0bb51396d3bc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 04:12:27 +0900 Subject: [PATCH 026/144] Benchmark: Add creation from small integers --- benchmark/from_integer.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/benchmark/from_integer.yml b/benchmark/from_integer.yml index f98c5722..f17c8374 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_integer.yml @@ -11,7 +11,11 @@ contexts: prelude: |- figs = (0..9).to_a + big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: + big_n9: BigDecimal(big_n9) + big_n19: BigDecimal(big_n19) big_n10000: BigDecimal(big_n10000) From 3429bd7e6f7059a1e25a0b6d54b692b8bfc65ca7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 04:13:12 +0900 Subject: [PATCH 027/144] Implement special conversions for 64-bit integers This change improves the conversion speed from small integers. ``` Comparison: big_n9 master: 4003688.9 i/s bigdecimal 3.0.0: 1270551.0 i/s - 3.15x slower big_n19 master: 5410096.4 i/s bigdecimal 3.0.0: 1000250.3 i/s - 5.41x slower ``` --- bigdecimal.gemspec | 3 + ext/bigdecimal/bigdecimal.c | 85 ++++++++++++++++-- ext/bigdecimal/bits.h | 137 +++++++++++++++++++++++++++++ ext/bigdecimal/extconf.rb | 21 +++++ ext/bigdecimal/feature.h | 68 ++++++++++++++ ext/bigdecimal/static_assert.h | 54 ++++++++++++ test/bigdecimal/test_bigdecimal.rb | 17 ++++ 7 files changed, 378 insertions(+), 7 deletions(-) create mode 100644 ext/bigdecimal/bits.h create mode 100644 ext/bigdecimal/feature.h create mode 100644 ext/bigdecimal/static_assert.h diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 15d4e146..c8be0770 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -19,6 +19,9 @@ Gem::Specification.new do |s| bigdecimal.gemspec ext/bigdecimal/bigdecimal.c ext/bigdecimal/bigdecimal.h + ext/bigdecimal/bits.h + ext/bigdecimal/feature.h + ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb lib/bigdecimal/ludcmp.rb diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 96fe253d..5a692428 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -30,18 +30,19 @@ #include #endif +#include "bits.h" +#include "static_assert.h" + /* #define ENABLE_NUMERIC_STRING */ -#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ - (a) == 0 ? 0 : \ - (a) == -1 ? (b) < -(max) : \ - (a) > 0 ? \ - ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ - ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) #define SIGNED_VALUE_MAX INTPTR_MAX #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -80,6 +81,8 @@ static ID id_half; #define DBLE_FIG rmpd_double_figures() /* figure of double */ #endif +#define LOG10_2 0.3010299956639812 + #ifndef RRATIONAL_ZERO_P # define RRATIONAL_ZERO_P(x) (FIXNUM_P(rb_rational_num(x)) && \ FIX2LONG(rb_rational_num(x)) == 0) @@ -2754,12 +2757,80 @@ check_exception(VALUE bd) } static VALUE -rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + VALUE obj = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); + + Real *vp; + if (uval == 0) { + vp = VpAllocReal(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetZero(vp, 1); + vp->frac[0] = 0; + } + else if (uval < BASE) { + vp = VpAllocReal(1); + vp->MaxPrec = 1; + vp->Prec = 1; + vp->exponent = 1; + VpSetSign(vp, 1); + vp->frac[0] = (BDIGIT)uval; + } + else { + const size_t len10 = ceil(LOG10_2 * bit_length(uval)); + size_t len = roomof(len10, BASE_FIG); + + vp = VpAllocReal(len); + vp->MaxPrec = len; + vp->Prec = len; + vp->exponent = len; + VpSetSign(vp, 1); + + size_t i; + for (i = 0; i < len; ++i) { + BDIGIT r = uval % BASE; + vp->frac[len - i - 1] = r; + uval /= BASE; + } + } + + return BigDecimal_wrap_struct(obj, vp); +} + +static VALUE +rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) +{ + const uint64_t uval = (ival < 0) ? (((uint64_t)-(ival+1))+1) : (uint64_t)ival; + VALUE bd = rb_uint64_convert_to_BigDecimal(uval, digs, raise_exception); + if (ival < 0) { + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + VpSetSign(vp, -1); + } + return bd; +} + +static VALUE +rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) { Real *vp = GetVpValue(val, 1); return check_exception(vp->obj); } +static VALUE +rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) +{ + assert(RB_INTEGER_TYPE_P(val)); + if (FIXNUM_P(val)) { + return rb_int64_convert_to_BigDecimal(FIX2LONG(val), digs, raise_exception); + } + else { + return rb_big_convert_to_BigDecimal(val, digs, raise_exception); + } +} + static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h new file mode 100644 index 00000000..5dfda497 --- /dev/null +++ b/ext/bigdecimal/bits.h @@ -0,0 +1,137 @@ +#ifndef BIGDECIMAL_BITS_H +#define BIGDECIMAL_BITS_H + +#include "feature.h" +#include "static_assert.h" + +#if defined(HAVE_X86INTRIN_H) +# include /* for _lzcnt_u64 */ +#elif defined(_MSC_VER) && _MSC_VER >= 1310 +# include /* for the following intrinsics */ +#endif + +#if defined(_MSC_VER) && defined(__AVX2__) +# pragma intrinsic(__lzcnt) +# pragma intrinsic(__lzcnt64) +#endif + +#define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ + (a) == 0 ? 0 : \ + (a) == -1 ? (b) < -(max) : \ + (a) > 0 ? \ + ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ + ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) + +#ifdef HAVE_UINT128_T +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + sizeof(x) <= sizeof(int64_t) ? 64 - nlz_int64((uint64_t)(x)) : \ + 128 - nlz_int128((uint128_t)(x))) +#else +# define bit_length(x) \ + (unsigned int) \ + (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ + 64 - nlz_int64((uint64_t)(x))) +#endif + +static inline unsigned nlz_int32(uint32_t x); +static inline unsigned nlz_int64(uint64_t x); +#ifdef HAVE_UINT128_T +static inline unsigned nlz_int128(uint128_t x); +#endif + +static inline unsigned int +nlz_int32(uint32_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) + /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. + * AMD CPUs have had this instruction for decades (since K10) but for + * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum + * safety. */ + return (unsigned int)__lzcnt(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ + return (unsigned int)_lzcnt_u32(x); + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ + unsigned long r; + return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; + +#elif __has_builtin(__builtin_clz) + STATIC_ASSERT(sizeof_int, sizeof(int) * CHAR_BIT == 32); + return x ? (unsigned int)__builtin_clz(x) : 32; + +#else + uint32_t y; + unsigned n = 32; + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); +#endif +} + +static inline unsigned int +nlz_int64(uint64_t x) +{ +#if defined(_MSC_VER) && defined(__AVX2__) + return (unsigned int)__lzcnt64(x); + +#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ + return (unsigned int)_lzcnt_u64(x); + +#elif defined(_WIN64) && defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ + unsigned long r; + return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; + +#elif __has_builtin(__builtin_clzl) + if (x == 0) { + return 64; + } + else if (sizeof(long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzl((unsigned long)x); + } + else if (sizeof(long long) * CHAR_BIT == 64) { + return (unsigned int)__builtin_clzll((unsigned long long)x); + } + else { + /* :FIXME: Is there a way to make this branch a compile-time error? */ + __builtin_unreachable(); + } + +#else + uint64_t y; + unsigned int n = 64; + y = x >> 32; if (y) {n -= 32; x = y;} + y = x >> 16; if (y) {n -= 16; x = y;} + y = x >> 8; if (y) {n -= 8; x = y;} + y = x >> 4; if (y) {n -= 4; x = y;} + y = x >> 2; if (y) {n -= 2; x = y;} + y = x >> 1; if (y) {return n - 2;} + return (unsigned int)(n - x); + +#endif +} + +#ifdef HAVE_UINT128_T +static inline unsigned int +nlz_int128(uint128_t x) +{ + uint64_t y = (uint64_t)(x >> 64); + + if (x == 0) { + return 128; + } + else if (y == 0) { + return (unsigned int)nlz_int64(x) + 64; + } + else { + return (unsigned int)nlz_int64(y); + } +} +#endif + +#endif /* BIGDECIMAL_BITS_H */ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 41b8df8d..ab856b8d 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -16,6 +16,20 @@ def check_bigdecimal_version(gemspec_path) message "#{bigdecimal_version}\n" end +def have_builtin_func(name, check_expr, opt = "", &b) + checking_for checking_message(name.funcall_style, nil, opt) do + if try_compile(< +#endif + +#ifdef RBIMPL_HAS_BUILTIN +# define BIGDECIMAL_HAS_BUILTIN(...) RBIMPL_HAS_BUILTIN(__VA_ARGS__) + +#else +# /* The following section is copied from CRuby's builtin.h */ +# +# ifdef __has_builtin +# if defined(__INTEL_COMPILER) +# /* :TODO: Intel C Compiler has __has_builtin (since 19.1 maybe?), and is +# * reportedly broken. We have to skip them. However the situation can +# * change. They might improve someday. We need to revisit here later. */ +# elif defined(__GNUC__) && ! __has_builtin(__builtin_alloca) +# /* FreeBSD's defines its own *broken* version of +# * __has_builtin. Cygwin copied that content to be a victim of the +# * broken-ness. We don't take them into account. */ +# else +# define HAVE___HAS_BUILTIN 1 +# endif +# endif +# +# if defined(HAVE___HAS_BUILTIN) +# define BIGDECIMAL_HAS_BUILTIN(_) __has_builtin(_) +# +# elif defined(__GNUC__) +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 1 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 1 +# else +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz 0 +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 0 +# endif +# elif defined(_MSC_VER) +# define BIGDECIMAL_HAS_BUILTIN(_) 0 +# +# else +# define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clz HAVE_BUILTIN___BUILTIN_CLZ +# define BIGDECIMAL_HAS_BUILTIN___builtin_clzl HAVE_BUILTIN___BUILTIN_CLZL +# endif +#endif /* RBIMPL_HAS_BUILTIN */ + +#ifndef __has_builtin +# define __has_builtin(...) BIGDECIMAL_HAS_BUILTIN(__VA_ARGS__) +#endif + +#endif /* BIGDECIMAL_HAS_FEATURE_H */ diff --git a/ext/bigdecimal/static_assert.h b/ext/bigdecimal/static_assert.h new file mode 100644 index 00000000..9295729b --- /dev/null +++ b/ext/bigdecimal/static_assert.h @@ -0,0 +1,54 @@ +#ifndef BIGDECIMAL_STATIC_ASSERT_H +#define BIGDECIMAL_STATIC_ASSERT_H + +#include "feature.h" + +#ifdef HAVE_RUBY_INTERNAL_STATIC_ASSERT_H +# include +#endif + +#ifdef RBIMPL_STATIC_ASSERT +# define STATIC_ASSERT RBIMPL_STATIC_ASSERT +#endif + +#ifndef STATIC_ASSERT +# /* The following section is copied from CRuby's static_assert.h */ + +# if defined(__cplusplus) && defined(__cpp_static_assert) +# /* https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations */ +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1600 +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__INTEL_CXX11_MODE__) +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __cplusplus >= 201103L +# define BIGDECIMAL_STATIC_ASSERT0 static_assert + +# elif defined(__cplusplus) && __has_extension(cxx_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ static_assert + +# elif defined(__STDC_VERSION__) && __has_extension(c_static_assert) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert + +# elif defined(__STDC_VERSION__) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert +#endif + +# if defined(__DOXYGEN__) +# define STATIC_ASSERT static_assert + +# elif defined(BIGDECIMAL_STATIC_ASSERT0) +# define STATIC_ASSERT(name, expr) \ + BIGDECIMAL_STATIC_ASSERT0(expr, #name ": " #expr) + +# else +# define STATIC_ASSERT(name, expr) \ + typedef int static_assert_ ## name ## _check[1 - 2 * !(expr)] +# endif +#endif /* STATIC_ASSERT */ + + +#endif /* BIGDECIMAL_STATIC_ASSERT_H */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 6ce9c3b8..8d90972b 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,6 +1,7 @@ # frozen_string_literal: false require_relative "testbase" require 'bigdecimal/math' +require 'rbconfig/sizeof' class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase @@ -104,10 +105,26 @@ def test_BigDecimal_with_invalid_string end def test_BigDecimal_with_integer + assert_equal(BigDecimal("0"), BigDecimal(0)) assert_equal(BigDecimal("1"), BigDecimal(1)) assert_equal(BigDecimal("-1"), BigDecimal(-1)) assert_equal(BigDecimal((2**100).to_s), BigDecimal(2**100)) assert_equal(BigDecimal((-2**100).to_s), BigDecimal(-2**100)) + + assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"].to_s), + BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MIN"].to_s), + BigDecimal(RbConfig::LIMITS["INT64_MIN"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["INT64_MAX"])) + + assert_equal(BigDecimal(RbConfig::LIMITS["UINT64_MAX"].to_s), + BigDecimal(RbConfig::LIMITS["UINT64_MAX"])) end def test_BigDecimal_with_rational From c8087523b0498f650b9e5ea06b00a345da9d07bc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 16:00:23 +0900 Subject: [PATCH 028/144] Fix test for Ruby 2.4 Ruby 2.4 does not have RbConfig::LIMITS. --- bigdecimal.gemspec | 1 + test/bigdecimal/test_bigdecimal.rb | 30 ++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index c8be0770..ed33de8b 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -36,6 +36,7 @@ Gem::Specification.new do |s| s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") s.add_development_dependency "benchmark_driver" + s.add_development_dependency "fiddle" s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8d90972b..070d426e 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -6,6 +6,21 @@ class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase + if defined? RbConfig::LIMITS + LIMITS = RbConfig::LIMITS + else + require 'fiddle' + LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1 + LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0] + LIMITS = { + "FIXNUM_MIN" => LONG_MIN / 2, + "FIXNUM_MAX" => LONG_MAX / 2, + "INT64_MIN" => -9223372036854775808, + "INT64_MAX" => 9223372036854775807, + "UINT64_MAX" => 18446744073709551615, + }.freeze + end + ROUNDING_MODE_MAP = [ [ BigDecimal::ROUND_UP, :up], [ BigDecimal::ROUND_DOWN, :down], @@ -111,20 +126,15 @@ def test_BigDecimal_with_integer assert_equal(BigDecimal((2**100).to_s), BigDecimal(2**100)) assert_equal(BigDecimal((-2**100).to_s), BigDecimal(-2**100)) - assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"].to_s), - BigDecimal(RbConfig::LIMITS["FIXNUM_MIN"])) + assert_equal(BigDecimal(LIMITS["FIXNUM_MIN"].to_s), BigDecimal(LIMITS["FIXNUM_MIN"])) - assert_equal(BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["FIXNUM_MAX"])) + assert_equal(BigDecimal(LIMITS["FIXNUM_MAX"].to_s), BigDecimal(LIMITS["FIXNUM_MAX"])) - assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MIN"].to_s), - BigDecimal(RbConfig::LIMITS["INT64_MIN"])) + assert_equal(BigDecimal(LIMITS["INT64_MIN"].to_s), BigDecimal(LIMITS["INT64_MIN"])) - assert_equal(BigDecimal(RbConfig::LIMITS["INT64_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["INT64_MAX"])) + assert_equal(BigDecimal(LIMITS["INT64_MAX"].to_s), BigDecimal(LIMITS["INT64_MAX"])) - assert_equal(BigDecimal(RbConfig::LIMITS["UINT64_MAX"].to_s), - BigDecimal(RbConfig::LIMITS["UINT64_MAX"])) + assert_equal(BigDecimal(LIMITS["UINT64_MAX"].to_s), BigDecimal(LIMITS["UINT64_MAX"])) end def test_BigDecimal_with_rational From 9bf7a04a3bb31f67fbe7770142c090c9ae7a0dc8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 1 Jan 2021 16:06:29 +0900 Subject: [PATCH 029/144] Fix benchmark workflow --- .github/workflows/benchmark.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 7ae02d23..8c9aa8d8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -38,6 +38,7 @@ jobs: run: | bundle install gem install bigdecimal -v 3.0.0 - rake install + + - run: rake compile - run: rake benchmark From bdc1cc6585cf6b1384d5252586403fb26784fb7d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 00:27:46 +0900 Subject: [PATCH 030/144] Fix test_limit Keep the default value of BigDecimal.limit by BigDecimal.save_limit to avoid failures of the other test methods due to the unexpected limit. --- test/bigdecimal/test_bigdecimal.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 070d426e..e9a86cfb 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1519,16 +1519,17 @@ def test_power_with_prec end def test_limit - BigDecimal.limit(1) - x = BigDecimal("3") - assert_equal(90, x ** 4) # OK? must it be 80? - # 3 * 3 * 3 * 3 = 10 * 3 * 3 = 30 * 3 = 90 ??? - assert_raise(ArgumentError) { BigDecimal.limit(-1) } - - bug7458 = '[ruby-core:50269] [#7458]' - one = BigDecimal('1') - epsilon = BigDecimal('0.7E-18') BigDecimal.save_limit do + BigDecimal.limit(1) + x = BigDecimal("3") + assert_equal(90, x ** 4) # OK? must it be 80? + # 3 * 3 * 3 * 3 = 10 * 3 * 3 = 30 * 3 = 90 ??? + assert_raise(ArgumentError) { BigDecimal.limit(-1) } + + bug7458 = '[ruby-core:50269] [#7458]' + one = BigDecimal('1') + epsilon = BigDecimal('0.7E-18') + BigDecimal.limit(0) assert_equal(BigDecimal("1.0000000000000000007"), one + epsilon, "limit(0) #{bug7458}") From 61853fb6d63df41bae6ae0c5e719437affc8984e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 01:09:22 +0900 Subject: [PATCH 031/144] Benchmark: Split into small and large integers --- benchmark/from_large_integer.yml | 17 +++++++++++++++++ ...{from_integer.yml => from_small_integer.yml} | 4 +--- 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 benchmark/from_large_integer.yml rename benchmark/{from_integer.yml => from_small_integer.yml} (75%) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml new file mode 100644 index 00000000..5d65938c --- /dev/null +++ b/benchmark/from_large_integer.yml @@ -0,0 +1,17 @@ +loop_count: 1000 + +contexts: +- gems: + bigdecimal: 3.0.0 +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + figs = (0..9).to_a + + big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + +benchmark: + big_n10000: BigDecimal(big_n10000) diff --git a/benchmark/from_integer.yml b/benchmark/from_small_integer.yml similarity index 75% rename from benchmark/from_integer.yml rename to benchmark/from_small_integer.yml index f17c8374..021aa7d9 100644 --- a/benchmark/from_integer.yml +++ b/benchmark/from_small_integer.yml @@ -1,4 +1,4 @@ -loop_count: 100 +loop_count: 100000 contexts: - gems: @@ -13,9 +13,7 @@ prelude: |- big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } - big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: big_n9: BigDecimal(big_n9) big_n19: BigDecimal(big_n19) - big_n10000: BigDecimal(big_n10000) From 164e0361f9a1d9a8f3dfce6fdbc359ca0f4cdaef Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 2 Jan 2021 09:48:42 +0900 Subject: [PATCH 032/144] Check if x86intrin.h is available not only existing https://github.com/ruby/ruby/commit/5aa28d9d6d --- ext/bigdecimal/extconf.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index ab856b8d..4ad5172e 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -46,7 +46,9 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") have_header("stdbool.h") -have_header("x86intrin.h") +if have_func("_lzcnt_u64", "x86intrin.h") # check availability + $defs << "-DHAVE_X86INTRIN_H" +end have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From d01499a3a4776e88001c173aaf24150091034d7f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 10:18:58 +0900 Subject: [PATCH 033/144] Add __x86_64__ guard to include x86intrin.h https://github.com/ruby/ruby/commit/ef6ab776d5 --- ext/bigdecimal/bits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index 5dfda497..ea9cb108 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -4,7 +4,7 @@ #include "feature.h" #include "static_assert.h" -#if defined(HAVE_X86INTRIN_H) +#if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) # include /* for _lzcnt_u64 */ #elif defined(_MSC_VER) && _MSC_VER >= 1310 # include /* for the following intrinsics */ From e67faafd569bf85e5a4c3a25690fedf67cd3f678 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 2 Jan 2021 12:06:14 +0900 Subject: [PATCH 034/144] Avoid to use __builtin_clzl in SPARC Solaris https://github.com/ruby/ruby/commit/a6bbba1135 --- ext/bigdecimal/bits.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index ea9cb108..f39e83b4 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -87,7 +87,7 @@ nlz_int64(uint64_t x) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) +#elif __has_builtin(__builtin_clzl) && !(defined(__sun) && defined(__sparc)) if (x == 0) { return 64; } From 4a58eddca4e291bed8eb66273fe9ddee58e10704 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 3 Jan 2021 23:19:20 +0900 Subject: [PATCH 035/144] Benchmark: change benchmark names --- benchmark/from_large_integer.yml | 4 ++-- benchmark/from_small_integer.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/benchmark/from_large_integer.yml b/benchmark/from_large_integer.yml index 5d65938c..58437fe0 100644 --- a/benchmark/from_large_integer.yml +++ b/benchmark/from_large_integer.yml @@ -11,7 +11,7 @@ contexts: prelude: |- figs = (0..9).to_a - big_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_n10000: BigDecimal(big_n10000) + int_n10000: BigDecimal(int_n10000) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml index 021aa7d9..b6640a57 100644 --- a/benchmark/from_small_integer.yml +++ b/benchmark/from_small_integer.yml @@ -11,9 +11,9 @@ contexts: prelude: |- figs = (0..9).to_a - big_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } - big_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: - big_n9: BigDecimal(big_n9) - big_n19: BigDecimal(big_n19) + int_n9: BigDecimal(int_n9) + int_n19: BigDecimal(int_n19) From c2b22cc8b3a086dd72271287dc3107b7071e115b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 12:10:09 +0900 Subject: [PATCH 036/144] Add HAVE_FLOAT_H guard --- ext/bigdecimal/bigdecimal.h | 6 ++++-- ext/bigdecimal/extconf.rb | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 900ebd41..e6a0cd52 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -10,9 +10,11 @@ #define RUBY_BIG_DECIMAL_H 1 #define RUBY_NO_OLD_COMPATIBILITY - #include "ruby/ruby.h" -#include + +#ifdef HAVE_FLOAT_H +# include +#endif #if defined(__bool_true_false_are_defined) # /* Take that. */ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 4ad5172e..463cf1bb 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -45,6 +45,7 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clz", "__builtin_clz(0)") have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") +have_header("float.h") have_header("stdbool.h") if have_func("_lzcnt_u64", "x86intrin.h") # check availability $defs << "-DHAVE_X86INTRIN_H" From 8cbca8481d644a1b329de54bd502d73b84d80bf2 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 12:55:22 +0900 Subject: [PATCH 037/144] Move some definitions to missing.h --- ext/bigdecimal/bigdecimal.c | 52 -------- ext/bigdecimal/bigdecimal.h | 116 +----------------- ext/bigdecimal/bits.h | 4 + ext/bigdecimal/extconf.rb | 3 + ext/bigdecimal/missing.h | 232 ++++++++++++++++++++++++++++++++++++ 5 files changed, 240 insertions(+), 167 deletions(-) create mode 100644 ext/bigdecimal/missing.h diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5a692428..b4f71142 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -39,10 +39,6 @@ #define SIGNED_VALUE_MIN INTPTR_MIN #define MUL_OVERFLOW_SIGNED_VALUE_P(a, b) MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, SIGNED_VALUE_MIN, SIGNED_VALUE_MAX) -#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) -#define roomof(x, y) (((x) + (y) - 1) / (y)) -#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) - VALUE rb_cBigDecimal; VALUE rb_mBigMath; @@ -106,54 +102,6 @@ static ID id_half; # define RB_OBJ_STRING(obj) StringValueCStr(obj) #endif -#ifndef HAVE_RB_RATIONAL_NUM -static inline VALUE -rb_rational_num(VALUE rat) -{ -#ifdef HAVE_TYPE_STRUCT_RRATIONAL - return RRATIONAL(rat)->num; -#else - return rb_funcall(rat, rb_intern("numerator"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_RATIONAL_DEN -static inline VALUE -rb_rational_den(VALUE rat) -{ -#ifdef HAVE_TYPE_STRUCT_RRATIONAL - return RRATIONAL(rat)->den; -#else - return rb_funcall(rat, rb_intern("denominator"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_COMPLEX_REAL -static inline VALUE -rb_complex_real(VALUE cmp) -{ -#ifdef HAVE_TYPE_STRUCT_RCOMPLEX - return RCOMPLEX(cmp)->real; -#else - return rb_funcall(cmp, rb_intern("real"), 0); -#endif -} -#endif - -#ifndef HAVE_RB_COMPLEX_IMAG -static inline VALUE -rb_complex_imag(VALUE cmp) -{ -#ifdef HAVE_TYPE_STRUCT_RCOMPLEX - return RCOMPLEX(cmp)->imag; -#else - return rb_funcall(cmp, rb_intern("imag"), 0); -#endif -} -#endif - #define BIGDECIMAL_POSITIVE_P(bd) ((bd)->sign > 0) #define BIGDECIMAL_NEGATIVE_P(bd) ((bd)->sign < 0) diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index e6a0cd52..12b9ee6a 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -11,37 +11,12 @@ #define RUBY_NO_OLD_COMPATIBILITY #include "ruby/ruby.h" +#include "missing.h" #ifdef HAVE_FLOAT_H # include #endif -#if defined(__bool_true_false_are_defined) -# /* Take that. */ - -#elif defined(HAVE_STDBOOL_H) -# include - -#else -typedef unsigned char _Bool; -# define bool _Bool -# define true ((_Bool)+1) -# define false ((_Bool)-1) -# define __bool_true_false_are_defined -#endif - -#ifndef RB_UNUSED_VAR -# ifdef __GNUC__ -# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) -# else -# define RB_UNUSED_VAR(x) x -# endif -#endif - -#ifndef UNREACHABLE -# define UNREACHABLE /* unreachable */ -#endif - #undef BDIGIT #undef SIZEOF_BDIGITS #undef BDIGIT_DBL @@ -90,95 +65,6 @@ extern "C" { #endif #endif -#ifndef HAVE_LABS -static inline long -labs(long const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifndef HAVE_LLABS -static inline LONG_LONG -llabs(LONG_LONG const x) -{ - if (x < 0) return -x; - return x; -} -#endif - -#ifndef HAVE_FINITE -static int -finite(double) -{ - return !isnan(n) && !isinf(n); -} -#endif - -#ifndef isfinite -# ifndef HAVE_ISFINITE -# define HAVE_ISFINITE 1 -# define isfinite(x) finite(x) -# endif -#endif - -#ifndef FIX_CONST_VALUE_PTR -# if defined(__fcc__) || defined(__fcc_version) || \ - defined(__FCC__) || defined(__FCC_VERSION) -/* workaround for old version of Fujitsu C Compiler (fcc) */ -# define FIX_CONST_VALUE_PTR(x) ((const VALUE *)(x)) -# else -# define FIX_CONST_VALUE_PTR(x) (x) -# endif -#endif - -#ifndef HAVE_RB_ARRAY_CONST_PTR -static inline const VALUE * -rb_array_const_ptr(VALUE a) -{ - return FIX_CONST_VALUE_PTR((RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? - RARRAY(a)->as.ary : RARRAY(a)->as.heap.ptr); -} -#endif - -#ifndef RARRAY_CONST_PTR -# define RARRAY_CONST_PTR(a) rb_array_const_ptr(a) -#endif - -#ifndef RARRAY_AREF -# define RARRAY_AREF(a, i) (RARRAY_CONST_PTR(a)[i]) -#endif - -#ifndef HAVE_RB_SYM2STR -static inline VALUE -rb_sym2str(VALUE sym) -{ - return rb_id2str(SYM2ID(sym)); -} -#endif - -#ifndef ST2FIX -# undef RB_ST2FIX -# define RB_ST2FIX(h) LONG2FIX((long)(h)) -# define ST2FIX(h) RB_ST2FIX(h) -#endif - -#ifdef vabs -# undef vabs -#endif -#if SIZEOF_VALUE <= SIZEOF_INT -# define vabs abs -#elif SIZEOF_VALUE <= SIZEOF_LONG -# define vabs labs -#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG -# define vabs llabs -#endif - -#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) -# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) -#endif - extern VALUE rb_cBigDecimal; #if 0 || SIZEOF_BDIGITS >= 16 diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index f39e83b4..3835fe3a 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -15,6 +15,10 @@ # pragma intrinsic(__lzcnt64) #endif +#define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) +#define roomof(x, y) (((x) + (y) - 1) / (y)) +#define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) + #define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ (a) == 0 ? 0 : \ (a) == -1 ? (b) < -(max) : \ diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 463cf1bb..5055e10f 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -46,7 +46,10 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") have_header("float.h") +have_header("math.h") have_header("stdbool.h") +have_header("stdlib.h") + if have_func("_lzcnt_u64", "x86intrin.h") # check availability $defs << "-DHAVE_X86INTRIN_H" end diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h new file mode 100644 index 00000000..aa056c32 --- /dev/null +++ b/ext/bigdecimal/missing.h @@ -0,0 +1,232 @@ +#ifndef MISSING_H +#define MISSING_H 1 + +#if defined(__cplusplus) +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifdef HAVE_STDLIB_H +# include +#endif + +#ifdef HAVE_MATH_H +# include +#endif + +#ifndef RB_UNUSED_VAR +# if defined(_MSC_VER) && _MSC_VER >= 1911 +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_cpp_attribute) && __has_cpp_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__has_c_attribute) && __has_c_attribute(maybe_unused) +# define RB_UNUSED_VAR(x) x [[maybe_unused]] + +# elif defined(__GNUC__) +# define RB_UNUSED_VAR(x) x __attribute__ ((unused)) + +# else +# define RB_UNUSED_VAR(x) x +# endif +#endif /* RB_UNUSED_VAR */ + +#if defined(_MSC_VER) && _MSC_VER >= 1310 +# define HAVE___ASSUME + +#elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 +# define HAVE___ASSUME +#endif + +#ifndef UNREACHABLE +# if __has_builtin(__builtin_unreachable) +# define UNREACHABLE __builtin_unreachable() + +# elif HAVE___ASSUME +# define UNREACHABLE __assume(0) + +# else +# define UNREACHABLE /* unreachable */ +# endif +#endif /* UNREACHABLE */ + +/* bool */ + +#if defined(__bool_true_false_are_defined) +# /* Take that. */ + +#elif defined(HAVE_STDBOOL_H) +# include + +#else +typedef unsigned char _Bool; +# define bool _Bool +# define true ((_Bool)+1) +# define false ((_Bool)-1) +# define __bool_true_false_are_defined +#endif + +/* abs */ + +#ifndef HAVE_LABS +static inline long +labs(long const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifndef HAVE_LLABS +static inline LONG_LONG +llabs(LONG_LONG const x) +{ + if (x < 0) return -x; + return x; +} +#endif + +#ifdef vabs +# undef vabs +#endif +#if SIZEOF_VALUE <= SIZEOF_INT +# define vabs abs +#elif SIZEOF_VALUE <= SIZEOF_LONG +# define vabs labs +#elif SIZEOF_VALUE <= SIZEOF_LONG_LONG +# define vabs llabs +#endif + +/* finite */ + +#ifndef HAVE_FINITE +static int +finite(double) +{ + return !isnan(n) && !isinf(n); +} +#endif + +#ifndef isfinite +# ifndef HAVE_ISFINITE +# define HAVE_ISFINITE 1 +# define isfinite(x) finite(x) +# endif +#endif + +/* rational */ + +#ifndef HAVE_RB_RATIONAL_NUM +static inline VALUE +rb_rational_num(VALUE rat) +{ +#ifdef HAVE_TYPE_STRUCT_RRATIONAL + return RRATIONAL(rat)->num; +#else + return rb_funcall(rat, rb_intern("numerator"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_RATIONAL_DEN +static inline VALUE +rb_rational_den(VALUE rat) +{ +#ifdef HAVE_TYPE_STRUCT_RRATIONAL + return RRATIONAL(rat)->den; +#else + return rb_funcall(rat, rb_intern("denominator"), 0); +#endif +} +#endif + +/* complex */ + +#ifndef HAVE_RB_COMPLEX_REAL +static inline VALUE +rb_complex_real(VALUE cmp) +{ +#ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->real; +#else + return rb_funcall(cmp, rb_intern("real"), 0); +#endif +} +#endif + +#ifndef HAVE_RB_COMPLEX_IMAG +static inline VALUE +rb_complex_imag(VALUE cmp) +{ +# ifdef HAVE_TYPE_STRUCT_RCOMPLEX + return RCOMPLEX(cmp)->imag; +# else + return rb_funcall(cmp, rb_intern("imag"), 0); +# endif +} +#endif + +/* array */ + +#ifndef FIX_CONST_VALUE_PTR +# if defined(__fcc__) || defined(__fcc_version) || \ + defined(__FCC__) || defined(__FCC_VERSION) +/* workaround for old version of Fujitsu C Compiler (fcc) */ +# define FIX_CONST_VALUE_PTR(x) ((const VALUE *)(x)) +# else +# define FIX_CONST_VALUE_PTR(x) (x) +# endif +#endif + +#ifndef HAVE_RB_ARRAY_CONST_PTR +static inline const VALUE * +rb_array_const_ptr(VALUE a) +{ + return FIX_CONST_VALUE_PTR((RBASIC(a)->flags & RARRAY_EMBED_FLAG) ? + RARRAY(a)->as.ary : RARRAY(a)->as.heap.ptr); +} +#endif + +#ifndef RARRAY_CONST_PTR +# define RARRAY_CONST_PTR(a) rb_array_const_ptr(a) +#endif + +#ifndef RARRAY_AREF +# define RARRAY_AREF(a, i) (RARRAY_CONST_PTR(a)[i]) +#endif + +/* symbol */ + +#ifndef HAVE_RB_SYM2STR +static inline VALUE +rb_sym2str(VALUE sym) +{ + return rb_id2str(SYM2ID(sym)); +} +#endif + +/* st */ + +#ifndef ST2FIX +# undef RB_ST2FIX +# define RB_ST2FIX(h) LONG2FIX((long)(h)) +# define ST2FIX(h) RB_ST2FIX(h) +#endif + +/* warning */ + +#if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) +# define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) +#endif + +#if defined(__cplusplus) +#if 0 +{ /* satisfy cc-mode */ +#endif +} /* extern "C" { */ +#endif + +#endif /* MISSING_H */ From f05aecf673eccba2ef4e8bf89597f80a0b02956e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 13:10:40 +0900 Subject: [PATCH 038/144] Add missing.h in bigdecimal.gemspec --- bigdecimal.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index ed33de8b..5dc35db7 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -21,6 +21,7 @@ Gem::Specification.new do |s| ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h ext/bigdecimal/feature.h + ext/bigdecimal/missing.h ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb From 9809878872b01c85d6c99094a544d2cd6f79ca7c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 4 Jan 2021 22:08:18 +0900 Subject: [PATCH 039/144] Benchmark: Add creation from Float --- benchmark/from_float.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 benchmark/from_float.yml diff --git a/benchmark/from_float.yml b/benchmark/from_float.yml new file mode 100644 index 00000000..25b362e8 --- /dev/null +++ b/benchmark/from_float.yml @@ -0,0 +1,23 @@ +loop_count: 100000 + +contexts: +- gems: + bigdecimal: 3.0.0 +- name: "master" + prelude: |- + $LOAD_PATH.unshift(File.expand_path("lib")) + require "bigdecimal" + +prelude: |- + flt_e0 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}".to_f + flt_ep10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+10".to_f + flt_ep100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+100".to_f + flt_em10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-10".to_f + flt_em100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-100".to_f + +benchmark: + flt_e0: BigDecimal(flt_e0, Float::DIG+1) + flt_ep10: BigDecimal(flt_ep10, Float::DIG+1) + flt_ep100: BigDecimal(flt_ep100, Float::DIG+1) + flt_em10: BigDecimal(flt_em10, Float::DIG+1) + flt_em100: BigDecimal(flt_em100, Float::DIG+1) From cf839a34c878d2283d2870685bbf2bc0ed7bc982 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 5 Jan 2021 08:12:39 +0900 Subject: [PATCH 040/144] Check the function availabilities separately --- ext/bigdecimal/bits.h | 18 +++++++++--------- ext/bigdecimal/extconf.rb | 13 ++++++++++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index 3835fe3a..f221119c 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -5,8 +5,8 @@ #include "static_assert.h" #if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) -# include /* for _lzcnt_u64 */ -#elif defined(_MSC_VER) && _MSC_VER >= 1310 +# include /* for _lzcnt_u64, etc. */ +#elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) # include /* for the following intrinsics */ #endif @@ -48,17 +48,17 @@ static inline unsigned nlz_int128(uint128_t x); static inline unsigned int nlz_int32(uint32_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT) /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. * AMD CPUs have had this instruction for decades (since K10) but for * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum * safety. */ return (unsigned int)__lzcnt(x); -#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ +#elif defined(__x86_64__) && defined(HAVE__LZCNT_U32) return (unsigned int)_lzcnt_u32(x); -#elif defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ +#elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) unsigned long r; return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; @@ -81,17 +81,17 @@ nlz_int32(uint32_t x) static inline unsigned int nlz_int64(uint64_t x) { -#if defined(_MSC_VER) && defined(__AVX2__) +#if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); -#elif defined(__x86_64__) && defined(__LZCNT__) /* && ! defined(MJIT_HEADER) */ +#elif defined(__x86_64__) && defined(HAVE__LZCNT_U64) return (unsigned int)_lzcnt_u64(x); -#elif defined(_WIN64) && defined(_MSC_VER) && _MSC_VER >= 1400 /* &&! defined(__AVX2__) */ +#elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) unsigned long r; return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; -#elif __has_builtin(__builtin_clzl) && !(defined(__sun) && defined(__sparc)) +#elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) if (x == 0) { return 64; } diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index 5055e10f..d5140e8a 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -44,15 +44,22 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_builtin_func("__builtin_clz", "__builtin_clz(0)") have_builtin_func("__builtin_clzl", "__builtin_clzl(0)") +have_builtin_func("__builtin_clzll", "__builtin_clzll(0)") have_header("float.h") have_header("math.h") have_header("stdbool.h") have_header("stdlib.h") -if have_func("_lzcnt_u64", "x86intrin.h") # check availability - $defs << "-DHAVE_X86INTRIN_H" -end +have_header("x86intrin.h") +have_func("_lzcnt_u32", "x86intrin.h") +have_func("_lzcnt_u64", "x86intrin.h") + +have_header("intrin.h") +have_func("__lzcnt", "intrin.h") +have_func("__lzcnt64", "intrin.h") +have_func("_BitScanReverse", "intrin.h") +have_func("_BitScanReverse64", "intrin.h") have_func("labs", "stdlib.h") have_func("llabs", "stdlib.h") From 75db4dabb98de54104e74de0c2ae1bf6317a9541 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 5 Jan 2021 15:50:38 +0900 Subject: [PATCH 041/144] Restore __LZCNT__ guard --- ext/bigdecimal/bits.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bits.h b/ext/bigdecimal/bits.h index f221119c..6e1e4776 100644 --- a/ext/bigdecimal/bits.h +++ b/ext/bigdecimal/bits.h @@ -55,7 +55,7 @@ nlz_int32(uint32_t x) * safety. */ return (unsigned int)__lzcnt(x); -#elif defined(__x86_64__) && defined(HAVE__LZCNT_U32) +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U32) return (unsigned int)_lzcnt_u32(x); #elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) @@ -84,7 +84,7 @@ nlz_int64(uint64_t x) #if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) return (unsigned int)__lzcnt64(x); -#elif defined(__x86_64__) && defined(HAVE__LZCNT_U64) +#elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) return (unsigned int)_lzcnt_u64(x); #elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) From 4792a917d806ca1059c952f489413073ea51bf01 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 10:25:45 +0900 Subject: [PATCH 042/144] Optimize the conversion from small Bignum --- ext/bigdecimal/bigdecimal.c | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b4f71142..a9129086 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2763,8 +2763,32 @@ rb_int64_convert_to_BigDecimal(int64_t ival, size_t digs, int raise_exception) static VALUE rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_exception) { - Real *vp = GetVpValue(val, 1); - return check_exception(vp->obj); + assert(RB_TYPE_P(val, T_BIGNUM)); + + size_t size = rb_absint_size(val, NULL); + int sign = rb_big_cmp(val, INT2FIX(0)); + if (size <= sizeof(long)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULONG(val), digs, raise_exception); + } + } +#if defined(SIZEOF_LONG_LONG) && SIZEOF_LONG < SIZEOF_LONG_LONG + else if (size <= sizeof(LONG_LONG)) { + if (sign < 0) { + return rb_int64_convert_to_BigDecimal(NUM2LL(val), digs, raise_exception); + } + else { + return rb_uint64_convert_to_BigDecimal(NUM2ULL(val), digs, raise_exception); + } + } +#endif + else { + Real *vp = GetVpValue(val, 1); + return check_exception(vp->obj); + } } static VALUE From 686487d9425d8953624c7ab5b17e8d58657b0002 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 11:54:17 +0900 Subject: [PATCH 043/144] Rename BDIGIT to DECDIG --- benchmark/from_small_integer.yml | 2 + ext/bigdecimal/bigdecimal.c | 198 +++++++++++++++---------------- ext/bigdecimal/bigdecimal.h | 81 ++++++------- 3 files changed, 138 insertions(+), 143 deletions(-) diff --git a/benchmark/from_small_integer.yml b/benchmark/from_small_integer.yml index b6640a57..26ce4107 100644 --- a/benchmark/from_small_integer.yml +++ b/benchmark/from_small_integer.yml @@ -13,7 +13,9 @@ prelude: |- int_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } int_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } + int_n38 = 37.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } benchmark: int_n9: BigDecimal(int_n9) int_n19: BigDecimal(int_n19) + int_n38: BigDecimal(int_n38) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a9129086..abb0681d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -115,7 +115,7 @@ static ID id_half; */ static unsigned short VpGetException(void); static void VpSetException(unsigned short f); -static void VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v); +static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); @@ -137,7 +137,7 @@ static size_t BigDecimal_memsize(const void *ptr) { const Real *pv = ptr; - return (sizeof(*pv) + pv->MaxPrec * sizeof(BDIGIT)); + return (sizeof(*pv) + pv->MaxPrec * sizeof(DECDIG)); } #ifndef HAVE_RB_EXT_RACTOR_SAFE @@ -381,7 +381,7 @@ BigDecimal_precision(VALUE self) ex = 0; } else if (p->Prec > 0) { - BDIGIT x = p->frac[0]; + DECDIG x = p->frac[0]; for (precision = 0; x > 0; x /= 10) { ++precision; } @@ -397,7 +397,7 @@ BigDecimal_precision(VALUE self) precision += n * BASE_FIG; if (ex < (ssize_t)p->Prec) { - BDIGIT x = p->frac[n]; + DECDIG x = p->frac[n]; for (; x > 0 && x % 10 == 0; x /= 10) { --precision; } @@ -423,7 +423,7 @@ BigDecimal_n_significant_digits(VALUE self) int nlz, ntz; - BDIGIT x = p->frac[0]; + DECDIG x = p->frac[0]; for (nlz = BASE_FIG; x > 0; x /= 10) --nlz; x = p->frac[n-1]; @@ -452,8 +452,8 @@ BigDecimal_hash(VALUE self) hash = (st_index_t)p->sign; /* hash!=2: the case for 0(1),NaN(0) or +-Infinity(3) is sign itself */ if(hash == 2 || hash == (st_index_t)-2) { - hash ^= rb_memhash(p->frac, sizeof(BDIGIT)*p->Prec); - hash += p->exponent; + hash ^= rb_memhash(p->frac, sizeof(DECDIG)*p->Prec); + hash += p->exponent; } return ST2FIX(hash); } @@ -760,8 +760,8 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) return VpNewRbClass(mx, str, rb_cBigDecimal, true, raise_exception); } -#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) -#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(BDIGIT)) +#define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) +#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(DECDIG)) static Real * VpCopy(Real *pv, Real const* const x) @@ -774,7 +774,7 @@ VpCopy(Real *pv, Real const* const x) pv->exponent = x->exponent; pv->sign = x->sign; pv->flag = x->flag; - MEMCPY(pv->frac, x->frac, BDIGIT, pv->MaxPrec); + MEMCPY(pv->frac, x->frac, DECDIG, pv->MaxPrec); return pv; } @@ -836,7 +836,7 @@ BigDecimal_to_i(VALUE self) if (e <= 0) return INT2FIX(0); nf = VpBaseFig(); if (e <= nf) { - return LONG2NUM((long)(VpGetSign(p) * (BDIGIT_DBL_SIGNED)p->frac[0])); + return LONG2NUM((long)(VpGetSign(p) * (DECDIG_DBL_SIGNED)p->frac[0])); } else { VALUE a = BigDecimal_split(self); @@ -1424,7 +1424,7 @@ BigDecimal_div(VALUE self, VALUE r) */ /* Round */ if (VpHasVal(div)) { /* frac[0] must be zero for NaN,INF,Zero */ - VpInternalRound(c, 0, c->frac[c->Prec-1], (BDIGIT)(VpBaseVal() * (BDIGIT_DBL)res->frac[0] / div->frac[0])); + VpInternalRound(c, 0, c->frac[c->Prec-1], (DECDIG)(VpBaseVal() * (DECDIG_DBL)res->frac[0] / div->frac[0])); } return VpCheckGetValue(c); } @@ -2724,7 +2724,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->Prec = 1; vp->exponent = 1; VpSetSign(vp, 1); - vp->frac[0] = (BDIGIT)uval; + vp->frac[0] = (DECDIG)uval; } else { const size_t len10 = ceil(LOG10_2 * bit_length(uval)); @@ -2738,7 +2738,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r size_t i; for (i = 0; i < len; ++i) { - BDIGIT r = uval % BASE; + DECDIG r = uval % BASE; vp->frac[len - i - 1] = r; uval /= BASE; } @@ -3809,9 +3809,9 @@ enum op_sw { static int VpIsDefOP(Real *c, Real *a, Real *b, enum op_sw sw); static int AddExponent(Real *a, SIGNED_VALUE n); -static BDIGIT VpAddAbs(Real *a,Real *b,Real *c); -static BDIGIT VpSubAbs(Real *a,Real *b,Real *c); -static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, BDIGIT *av, BDIGIT *bv); +static DECDIG VpAddAbs(Real *a,Real *b,Real *c); +static DECDIG VpSubAbs(Real *a,Real *b,Real *c); +static size_t VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv); static int VpNmlz(Real *a); static void VpFormatSt(char *psz, size_t fFmt); static int VpRdup(Real *m, size_t ind_m); @@ -4215,13 +4215,13 @@ VpNumOfChars(Real *vp,const char *pszFmt) * that BASE is as large as possible satisfying the * relation MaxVal <= BASE*(BASE+1). Where the value * MaxVal is the largest value which can be represented - * by one BDIGIT word in the computer used. + * by one DECDIG word in the computer used. * * [Returns] * DBLE_FIG ... OK */ VP_EXPORT size_t -VpInit(BDIGIT BaseVal) +VpInit(DECDIG BaseVal) { /* Setup +/- Inf NaN -0 */ VpGetDoubleNegZero(); @@ -4236,12 +4236,12 @@ VpInit(BDIGIT BaseVal) #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - printf("VpInit: BaseVal = %"PRIuBDIGIT"\n", BaseVal); - printf("\tBASE = %"PRIuBDIGIT"\n", BASE); - printf("\tHALF_BASE = %"PRIuBDIGIT"\n", HALF_BASE); - printf("\tBASE1 = %"PRIuBDIGIT"\n", BASE1); - printf("\tBASE_FIG = %u\n", BASE_FIG); - printf("\tDBLE_FIG = %d\n", DBLE_FIG); + printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); + printf("\tBASE = %"PRIuDECDIG"\n", BASE); + printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); + printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); + printf("\tBASE_FIG = %u\n", BASE_FIG); + printf("\tDBLE_FIG = %d\n", DBLE_FIG); } #endif /* BIGDECIMAL_DEBUG */ @@ -4601,7 +4601,7 @@ VpAsgn(Real *c, Real *a, int isw) VpSetSign(c, isw * VpGetSign(a)); /* set sign */ n = (a->Prec < c->MaxPrec) ? (a->Prec) : (c->MaxPrec); c->Prec = n; - memcpy(c->frac, a->frac, n * sizeof(BDIGIT)); + memcpy(c->frac, a->frac, n * sizeof(DECDIG)); /* Needs round ? */ if (isw != 10) { /* Not in ActiveRound */ @@ -4632,7 +4632,7 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) short sw, isw; Real *a_ptr, *b_ptr; size_t n, na, nb, i; - BDIGIT mrv; + DECDIG mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4760,7 +4760,7 @@ VpAddSub(Real *c, Real *a, Real *b, int operation) * a and b assuming abs(a)>abs(b). * c = abs(a) + abs(b) ; where |a|>=|b| */ -static BDIGIT +static DECDIG VpAddAbs(Real *a, Real *b, Real *c) { size_t word_shift; @@ -4770,7 +4770,7 @@ VpAddAbs(Real *a, Real *b, Real *c) size_t a_pos; size_t b_pos, b_pos_with_word_shift; size_t c_pos; - BDIGIT av, bv, carry, mrv; + DECDIG av, bv, carry, mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4855,7 +4855,7 @@ VpAddAbs(Real *a, Real *b, Real *c) /* * c = abs(a) - abs(b) */ -static BDIGIT +static DECDIG VpSubAbs(Real *a, Real *b, Real *c) { size_t word_shift; @@ -4865,7 +4865,7 @@ VpSubAbs(Real *a, Real *b, Real *c) size_t a_pos; size_t b_pos, b_pos_with_word_shift; size_t c_pos; - BDIGIT av, bv, borrow, mrv; + DECDIG av, bv, borrow, mrv; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -4972,7 +4972,7 @@ VpSubAbs(Real *a, Real *b, Real *c) * c_pos = | */ static size_t -VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, BDIGIT *av, BDIGIT *bv) +VpSetPTR(Real *a, Real *b, Real *c, size_t *a_pos, size_t *b_pos, size_t *c_pos, DECDIG *av, DECDIG *bv) { size_t left_word, right_word, word_shift; @@ -5087,8 +5087,8 @@ VpMult(Real *c, Real *a, Real *b) size_t MxIndA, MxIndB, MxIndAB, MxIndC; size_t ind_c, i, ii, nc; size_t ind_as, ind_ae, ind_bs; - BDIGIT carry; - BDIGIT_DBL s; + DECDIG carry; + DECDIG_DBL s; Real *w; #ifdef BIGDECIMAL_DEBUG @@ -5142,7 +5142,7 @@ VpMult(Real *c, Real *a, Real *b) VpSetSign(c, VpGetSign(a) * VpGetSign(b)); /* set sign */ carry = 0; nc = ind_c = MxIndAB; - memset(c->frac, 0, (nc + 1) * sizeof(BDIGIT)); /* Initialize c */ + memset(c->frac, 0, (nc + 1) * sizeof(DECDIG)); /* Initialize c */ c->Prec = nc + 1; /* set precision */ for (nc = 0; nc < MxIndAB; ++nc, --ind_c) { if (nc < MxIndB) { /* The left triangle of the Fig. */ @@ -5162,15 +5162,15 @@ VpMult(Real *c, Real *a, Real *b) } for (i = ind_as; i <= ind_ae; ++i) { - s = (BDIGIT_DBL)a->frac[i] * b->frac[ind_bs--]; - carry = (BDIGIT)(s / BASE); - s -= (BDIGIT_DBL)carry * BASE; - c->frac[ind_c] += (BDIGIT)s; - if (c->frac[ind_c] >= BASE) { - s = c->frac[ind_c] / BASE; - carry += (BDIGIT)s; - c->frac[ind_c] -= (BDIGIT)(s * BASE); - } + s = (DECDIG_DBL)a->frac[i] * b->frac[ind_bs--]; + carry = (DECDIG)(s / BASE); + s -= (DECDIG_DBL)carry * BASE; + c->frac[ind_c] += (DECDIG)s; + if (c->frac[ind_c] >= BASE) { + s = c->frac[ind_c] / BASE; + carry += (DECDIG)s; + c->frac[ind_c] -= (DECDIG)(s * BASE); + } if (carry) { ii = ind_c; while (ii-- > 0) { @@ -5216,9 +5216,9 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) size_t word_a, word_b, word_c, word_r; size_t i, n, ind_a, ind_b, ind_c, ind_r; size_t nLoop; - BDIGIT_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; - BDIGIT borrow, borrow1, borrow2; - BDIGIT_DBL qb; + DECDIG_DBL q, b1, b1p1, b1b2, b1b2p1, r1r2; + DECDIG borrow, borrow1, borrow2; + DECDIG_DBL qb; #ifdef BIGDECIMAL_DEBUG if (gfDebug) { @@ -5290,7 +5290,7 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) ++ind_c; continue; } - r1r2 = (BDIGIT_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; + r1r2 = (DECDIG_DBL)r->frac[ind_c] * BASE + r->frac[ind_c + 1]; if (r1r2 == b1b2) { /* The first two word digits is the same */ ind_b = 2; @@ -5327,17 +5327,17 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) /* The first two word digits is not the same, */ /* then compare magnitude, and divide actually. */ if (r1r2 >= b1b2p1) { - q = r1r2 / b1b2p1; /* q == (BDIGIT)q */ - c->frac[ind_c] += (BDIGIT)q; - ind_r = b->Prec + ind_c - 1; - goto sub_mult; + q = r1r2 / b1b2p1; /* q == (DECDIG)q */ + c->frac[ind_c] += (DECDIG)q; + ind_r = b->Prec + ind_c - 1; + goto sub_mult; } div_b1p1: - if (ind_c + 1 >= word_c) goto out_side; - q = r1r2 / b1p1; /* q == (BDIGIT)q */ - c->frac[ind_c + 1] += (BDIGIT)q; - ind_r = b->Prec + ind_c; + if (ind_c + 1 >= word_c) goto out_side; + q = r1r2 / b1p1; /* q == (DECDIG)q */ + c->frac[ind_c + 1] += (DECDIG)q; + ind_r = b->Prec + ind_c; sub_mult: borrow1 = borrow2 = 0; @@ -5349,16 +5349,16 @@ VpDivd(Real *c, Real *r, Real *a, Real *b) qb = q * b->frac[ind_b]; if (qb < BASE) borrow1 = 0; else { - borrow1 = (BDIGIT)(qb / BASE); - qb -= (BDIGIT_DBL)borrow1 * BASE; /* get qb < BASE */ + borrow1 = (DECDIG)(qb / BASE); + qb -= (DECDIG_DBL)borrow1 * BASE; /* get qb < BASE */ } if(r->frac[ind_r] < qb) { - r->frac[ind_r] += (BDIGIT)(BASE - qb); - borrow2 = borrow2 + borrow1 + 1; + r->frac[ind_r] += (DECDIG)(BASE - qb); + borrow2 = borrow2 + borrow1 + 1; } else { - r->frac[ind_r] -= (BDIGIT)qb; - borrow2 += borrow1; + r->frac[ind_r] -= (DECDIG)qb; + borrow2 += borrow1; } if (borrow2) { if(r->frac[ind_r - 1] < borrow2) { @@ -5440,9 +5440,9 @@ VpNmlz(Real *a) i = 0; while (a->frac[i] == 0) ++i; /* skip the first few zeros */ if (i) { - a->Prec -= i; - if (!AddExponent(a, -(SIGNED_VALUE)i)) return 0; - memmove(&a->frac[0], &a->frac[i], a->Prec*sizeof(BDIGIT)); + a->Prec -= i; + if (!AddExponent(a, -(SIGNED_VALUE)i)) return 0; + memmove(&a->frac[0], &a->frac[i], a->Prec*sizeof(DECDIG)); } return 1; } @@ -5565,7 +5565,7 @@ static int VPrint(FILE *fp, const char *cntl_chr, Real *a) { size_t i, j, nc, nd, ZeroSup, sep = 10; - BDIGIT m, e, nn; + DECDIG m, e, nn; j = 0; nd = nc = 0; /* nd : number of digits in fraction part(every 10 digits, */ @@ -5709,7 +5709,7 @@ VP_EXPORT void VpSzMantissa(Real *a,char *psz) { size_t i, n, ZeroSup; - BDIGIT_DBL m, e, nn; + DECDIG_DBL m, e, nn; if (VpIsNaN(a)) { sprintf(psz, SZ_NaN); @@ -5792,7 +5792,7 @@ VpToString(Real *a, char *psz, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n, ZeroSup; - BDIGIT shift, m, e, nn; + DECDIG shift, m, e, nn; char *pszSav = psz; ssize_t ex; @@ -5840,7 +5840,7 @@ VpToFString(Real *a, char *psz, size_t fFmt, int fPlus) /* fPlus = 0: default, 1: set ' ' before digits, 2: set '+' before digits. */ { size_t i, n; - BDIGIT m, e, nn; + DECDIG m, e, nn; char *pszSav = psz; ssize_t ex; @@ -5915,7 +5915,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con me = ne; signe = 1; exponent_overflow = 0; - memset(a->frac, 0, ma * sizeof(BDIGIT)); + memset(a->frac, 0, ma * sizeof(DECDIG)); if (ne > 0) { i = 0; if (exp_chr[0] == '-') { @@ -6130,7 +6130,7 @@ VpDtoV(Real *m, double d) { size_t ind_m, mm; SIGNED_VALUE ne; - BDIGIT i; + DECDIG i; double val, val2; if (isnan(d)) { @@ -6165,12 +6165,12 @@ VpDtoV(Real *m, double d) /* Now val = 0.xxxxx*BASE**ne */ mm = m->MaxPrec; - memset(m->frac, 0, mm * sizeof(BDIGIT)); + memset(m->frac, 0, mm * sizeof(DECDIG)); for (ind_m = 0; val > 0.0 && ind_m < mm; ind_m++) { - val *= (double)BASE; - i = (BDIGIT)val; - val -= (double)i; - m->frac[ind_m] = i; + val *= (double)BASE; + i = (DECDIG)val; + val -= (double)i; + m->frac[ind_m] = i; } if (ind_m >= mm) ind_m = mm - 1; VpSetSign(m, (d > 0.0) ? 1 : -1); @@ -6178,7 +6178,7 @@ VpDtoV(Real *m, double d) m->exponent = ne; VpInternalRound(m, 0, (m->Prec > 0) ? m->frac[m->Prec-1] : 0, - (BDIGIT)(val*(double)BASE)); + (DECDIG)(val*(double)BASE)); Exit: #ifdef BIGDECIMAL_DEBUG @@ -6374,8 +6374,8 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) /* exptoadd: number of digits needed to compensate negative nf */ int fracf, fracf_1further; ssize_t n,i,ix,ioffset, exptoadd; - BDIGIT v, shifter; - BDIGIT div; + DECDIG v, shifter; + DECDIG div; nf += y->exponent * (ssize_t)BASE_FIG; exptoadd=0; @@ -6397,8 +6397,8 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) n = (ssize_t)BASE_FIG - ioffset - 1; for (shifter = 1, i = 0; i < n; ++i) shifter *= 10; - /* so the representation used (in y->frac) is an array of BDIGIT, where - each BDIGIT contains a value between 0 and BASE-1, consisting of BASE_FIG + /* so the representation used (in y->frac) is an array of DECDIG, where + each DECDIG contains a value between 0 and BASE-1, consisting of BASE_FIG decimal places. (that numbers of decimal places are typed as ssize_t is somewhat confusing) @@ -6406,10 +6406,10 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) nf is now position (in decimal places) of the digit from the start of the array. - ix is the position (in BDIGITS) of the BDIGIT containing the decimal digit, + ix is the position (in DECDIGs) of the DECDIG containing the decimal digit, from the start of the array. - v is the value of this BDIGIT + v is the value of this DECDIG ioffset is the number of extra decimal places along of this decimal digit within v. @@ -6435,7 +6435,7 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) now fracf_1further is whether any of the remaining digits within v are non-zero */ - /* now check all the remaining BDIGITS for zero-ness a whole BDIGIT at a time. + /* now check all the remaining DECDIGs for zero-ness a whole DECDIG at a time. if we spot any non-zeroness, that means that we found a positive digit under rounding position, and we also found a positive digit under one further than the rounding position, so both searches (to see if any such non-zero digit exists) @@ -6454,7 +6454,7 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) now v = the first digit under the rounding position */ /* drop digits after pointed digit */ - memset(y->frac + ix + 1, 0, (y->Prec - (ix + 1)) * sizeof(BDIGIT)); + memset(y->frac + ix + 1, 0, (y->Prec - (ix + 1)) * sizeof(DECDIG)); switch (f) { case VP_ROUND_DOWN: /* Truncate */ @@ -6482,11 +6482,11 @@ VpMidRound(Real *y, unsigned short f, ssize_t nf) } else { if (ioffset == 0) { - /* v is the first decimal digit of its BDIGIT; - need to grab the previous BDIGIT if present - to check for evenness of the previous decimal - digit (which is same as that of the BDIGIT since - base 10 has a factor of 2) */ + /* v is the first decimal digit of its DECDIG; + need to grab the previous DECDIG if present + to check for evenness of the previous decimal + digit (which is same as that of the DECDIG since + base 10 has a factor of 2) */ if (ix && (y->frac[ix-1] % 2)) ++div; } else { @@ -6534,7 +6534,7 @@ VpLeftRound(Real *y, unsigned short f, ssize_t nf) * Round from the left hand side of the digits. */ { - BDIGIT v; + DECDIG v; if (!VpHasVal(y)) return 0; /* Unable to round */ v = y->frac[0]; nf -= VpExponent(y) * (ssize_t)BASE_FIG; @@ -6565,7 +6565,7 @@ VpLimitRound(Real *c, size_t ixDigit) /* If I understand correctly, this is only ever used to round off the final decimal digit of precision */ static void -VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v) +VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v) { int f = 0; @@ -6615,7 +6615,7 @@ VpInternalRound(Real *c, size_t ixDigit, BDIGIT vPrev, BDIGIT v) static int VpRdup(Real *m, size_t ind_m) { - BDIGIT carry; + DECDIG carry; if (!ind_m) ind_m = m->Prec; @@ -6810,12 +6810,12 @@ VpVarCheck(Real * v) } for (i = 0; i < v->Prec; ++i) { if (v->frac[i] >= BASE) { - printf("ERROR(VpVarCheck): Illegal fraction\n"); - printf(" Frac[%"PRIuSIZE"]=%"PRIuBDIGIT"\n", i, v->frac[i]); - printf(" Prec. =%"PRIuSIZE"\n", v->Prec); - printf(" Exp. =%"PRIdVALUE"\n", v->exponent); - printf(" BASE =%"PRIuBDIGIT"\n", BASE); - return 3; + printf("ERROR(VpVarCheck): Illegal fraction\n"); + printf(" Frac[%"PRIuSIZE"]=%"PRIuDECDIG"\n", i, v->frac[i]); + printf(" Prec. =%"PRIuSIZE"\n", v->Prec); + printf(" Exp. =%"PRIdVALUE"\n", v->exponent); + printf(" BASE =%"PRIuDECDIG"\n", BASE); + return 3; } } return 0; diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 12b9ee6a..6b6ac216 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -17,46 +17,39 @@ # include #endif -#undef BDIGIT -#undef SIZEOF_BDIGITS -#undef BDIGIT_DBL -#undef BDIGIT_DBL_SIGNED -#undef PRI_BDIGIT_PREFIX -#undef PRI_BDIGIT_DBL_PREFIX - #ifdef HAVE_INT64_T -# define BDIGIT uint32_t -# define BDIGIT_DBL uint64_t -# define BDIGIT_DBL_SIGNED int64_t -# define SIZEOF_BDIGITS 4 -# define PRI_BDIGIT_PREFIX "" +# define DECDIG uint32_t +# define DECDIG_DBL uint64_t +# define DECDIG_DBL_SIGNED int64_t +# define SIZEOF_DECDIG 4 +# define PRI_DECDIG_PREFIX "" # ifdef PRI_LL_PREFIX -# define PRI_BDIGIT_DBL_PREFIX PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX # else -# define PRI_BDIGIT_DBL_PREFIX "l" +# define PRI_DECDIG_DBL_PREFIX "l" # endif #else -# define BDIGIT uint16_t -# define BDIGIT_DBL uint32_t -# define BDIGIT_DBL_SIGNED int32_t -# define SIZEOF_BDIGITS 2 -# define PRI_BDIGIT_PREFIX "h" -# define PRI_BDIGIT_DBL_PREFIX "" +# define DECDIG uint16_t +# define DECDIG_DBL uint32_t +# define DECDIG_DBL_SIGNED int32_t +# define SIZEOF_DECDIG 2 +# define PRI_DECDIG_PREFIX "h" +# define PRI_DECDIG_DBL_PREFIX "" #endif -#define PRIdBDIGIT PRI_BDIGIT_PREFIX"d" -#define PRIiBDIGIT PRI_BDIGIT_PREFIX"i" -#define PRIoBDIGIT PRI_BDIGIT_PREFIX"o" -#define PRIuBDIGIT PRI_BDIGIT_PREFIX"u" -#define PRIxBDIGIT PRI_BDIGIT_PREFIX"x" -#define PRIXBDIGIT PRI_BDIGIT_PREFIX"X" +#define PRIdDECDIG PRI_DECDIG_PREFIX"d" +#define PRIiDECDIG PRI_DECDIG_PREFIX"i" +#define PRIoDECDIG PRI_DECDIG_PREFIX"o" +#define PRIuDECDIG PRI_DECDIG_PREFIX"u" +#define PRIxDECDIG PRI_DECDIG_PREFIX"x" +#define PRIXDECDIG PRI_DECDIG_PREFIX"X" -#define PRIdBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"d" -#define PRIiBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"i" -#define PRIoBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"o" -#define PRIuBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"u" -#define PRIxBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"x" -#define PRIXBDIGIT_DBL PRI_BDIGIT_DBL_PREFIX"X" +#define PRIdDECDIG_DBL PRI_DECDIG_DBL_PREFIX"d" +#define PRIiDECDIG_DBL PRI_DECDIG_DBL_PREFIX"i" +#define PRIoDECDIG_DBL PRI_DECDIG_DBL_PREFIX"o" +#define PRIuDECDIG_DBL PRI_DECDIG_DBL_PREFIX"u" +#define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" +#define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" #if defined(__cplusplus) extern "C" { @@ -67,21 +60,21 @@ extern "C" { extern VALUE rb_cBigDecimal; -#if 0 || SIZEOF_BDIGITS >= 16 +#if 0 || SIZEOF_DECDIG >= 16 # define RMPD_COMPONENT_FIGURES 38 -# define RMPD_BASE ((BDIGIT)100000000000000000000000000000000000000U) -#elif SIZEOF_BDIGITS >= 8 +# define RMPD_BASE ((DECDIG)100000000000000000000000000000000000000U) +#elif SIZEOF_DECDIG >= 8 # define RMPD_COMPONENT_FIGURES 19 -# define RMPD_BASE ((BDIGIT)10000000000000000000U) -#elif SIZEOF_BDIGITS >= 4 +# define RMPD_BASE ((DECDIG)10000000000000000000U) +#elif SIZEOF_DECDIG >= 4 # define RMPD_COMPONENT_FIGURES 9 -# define RMPD_BASE ((BDIGIT)1000000000U) -#elif SIZEOF_BDIGITS >= 2 +# define RMPD_BASE ((DECDIG)1000000000U) +#elif SIZEOF_DECDIG >= 2 # define RMPD_COMPONENT_FIGURES 4 -# define RMPD_BASE ((BDIGIT)10000U) +# define RMPD_BASE ((DECDIG)10000U) #else # define RMPD_COMPONENT_FIGURES 2 -# define RMPD_BASE ((BDIGIT)100U) +# define RMPD_BASE ((DECDIG)100U) #endif @@ -164,7 +157,7 @@ typedef struct { * -3 : Negative infinite number */ short flag; /* Not used in vp_routines,space for user. */ - BDIGIT frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ + DECDIG frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ } Real; /* @@ -177,7 +170,7 @@ VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool stric VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); -static inline BDIGIT +static inline DECDIG rmpd_base_value(void) { return RMPD_BASE; } static inline size_t rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } @@ -208,7 +201,7 @@ VP_EXPORT int VpException(unsigned short f,const char *str,int always); VP_EXPORT int VpIsNegDoubleZero(double v); #endif VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); -VP_EXPORT size_t VpInit(BDIGIT BaseVal); +VP_EXPORT size_t VpInit(DECDIG BaseVal); VP_EXPORT void *VpMemAlloc(size_t mb); VP_EXPORT void *VpMemRealloc(void *ptr, size_t mb); VP_EXPORT void VpFree(Real *pv); From c8de503f8d9713e06727e016c8c81292c4971373 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 13:39:09 +0900 Subject: [PATCH 044/144] Need to convert the return value of rb_big_cmp https://github.com/ruby/ruby/commit/7da06c04b2 --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index abb0681d..63de77cc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2766,7 +2766,7 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex assert(RB_TYPE_P(val, T_BIGNUM)); size_t size = rb_absint_size(val, NULL); - int sign = rb_big_cmp(val, INT2FIX(0)); + int sign = FIX2INT(rb_big_cmp(val, INT2FIX(0))); if (size <= sizeof(long)) { if (sign < 0) { return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); From f732201df1a9c6aefa75681e5abadb3dabcf4718 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 14:41:13 +0900 Subject: [PATCH 045/144] Include TestBigDecimalBase in TestBigDecimalUtil --- test/bigdecimal/{testbase.rb => helper.rb} | 0 test/bigdecimal/test_bigdecimal.rb | 2 +- test/bigdecimal/test_bigdecimal_util.rb | 5 +++-- test/bigdecimal/test_bigmath.rb | 2 +- test/bigdecimal/test_ractor.rb | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) rename test/bigdecimal/{testbase.rb => helper.rb} (100%) diff --git a/test/bigdecimal/testbase.rb b/test/bigdecimal/helper.rb similarity index 100% rename from test/bigdecimal/testbase.rb rename to test/bigdecimal/helper.rb diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e9a86cfb..f94cf9b6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,5 +1,5 @@ # frozen_string_literal: false -require_relative "testbase" +require_relative "helper" require 'bigdecimal/math' require 'rbconfig/sizeof' diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index b855fd58..3e3d9db1 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -1,9 +1,10 @@ # frozen_string_literal: false -require_relative "testbase" - +require_relative "helper" require 'bigdecimal/util' class TestBigDecimalUtil < Test::Unit::TestCase + include TestBigDecimalBase + def test_BigDecimal_to_d x = BigDecimal(1) assert_same(x, x.to_d) diff --git a/test/bigdecimal/test_bigmath.rb b/test/bigdecimal/test_bigmath.rb index 6f271d09..5bf1fbf3 100644 --- a/test/bigdecimal/test_bigmath.rb +++ b/test/bigdecimal/test_bigmath.rb @@ -1,5 +1,5 @@ # frozen_string_literal: false -require_relative "testbase" +require_relative "helper" require "bigdecimal/math" class TestBigMath < Test::Unit::TestCase diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 3ccd7c80..f78663f1 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require_relative "testbase" +require_relative "helper" class TestBigDecimalRactor < Test::Unit::TestCase include TestBigDecimalBase From 2056604d56d1985bdf145793b85071a22db638be Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 6 Jan 2021 18:17:35 +0900 Subject: [PATCH 046/144] Fix trailing zero handling in rb_uint64_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 5 ++++- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 63de77cc..72c7b348 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2736,12 +2736,15 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->exponent = len; VpSetSign(vp, 1); - size_t i; + size_t i, ntz = 0; for (i = 0; i < len; ++i) { DECDIG r = uval % BASE; vp->frac[len - i - 1] = r; + if (r == 0) ++ntz; uval /= BASE; } + + vp->Prec -= ntz; } return BigDecimal_wrap_struct(obj, vp); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f94cf9b6..12b25606 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1948,6 +1948,10 @@ def test_precision_only_integer assert_equal(1, BigDecimal(-1).precision) assert_equal(2, BigDecimal(10).precision) assert_equal(2, BigDecimal(-10).precision) + assert_equal(9, BigDecimal(100_000_000).precision) + assert_equal(9, BigDecimal(-100_000_000).precision) + assert_equal(12, BigDecimal(100_000_000_000).precision) + assert_equal(12, BigDecimal(-100_000_000_000).precision) assert_equal(21, BigDecimal(100_000_000_000_000_000_000).precision) assert_equal(21, BigDecimal(-100_000_000_000_000_000_000).precision) assert_equal(103, BigDecimal("111e100").precision) From 28d383636633366d891ed32540559843f4fcc7a4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 8 Jan 2021 21:00:45 +0900 Subject: [PATCH 047/144] Add test cases of conversion from Float --- test/bigdecimal/test_bigdecimal_util.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 3e3d9db1..9c6973dc 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -25,6 +25,8 @@ def test_Float_to_d_without_precision assert_equal(9.05, 9.05.to_d.to_f) assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) + assert_equal(Math::PI, Math::PI.to_d.to_f) + bug9214 = '[ruby-core:58858]' assert_equal((-0.0).to_d.sign, -1, bug9214) @@ -50,6 +52,10 @@ def test_Float_to_d_bug13331 assert_equal(64.4.to_d, 1.to_d * 64.4, "[ruby-core:80234] [Bug #13331]") + + assert_equal((2*Math::PI).to_d, + 2.to_d * Math::PI, + "[ruby-core:80234] [Bug #13331]") end def test_Rational_to_d From 33e7c502636aa1b11590922830dde7f1259f5f98 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:10:20 +0900 Subject: [PATCH 048/144] Stop using GetVpValueWithPrec in rb_float_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 72c7b348..5defd0f1 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2806,10 +2806,13 @@ rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_e } } +static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); + static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { double d = RFLOAT_VALUE(val); + if (!isfinite(d)) { Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ VpDtoV(vp, d); @@ -2829,7 +2832,18 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - Real *vp = GetVpValueWithPrec(val, digs, 1); + if (d != 0.0) { + val = rb_funcall(val, id_to_r, 0); + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + } + + Real *vp; + if (1/d < 0.0) { + vp = VpCreateRbObject(digs, "-0", true); + } + else { + vp = VpCreateRbObject(digs, "0", true); + } return check_exception(vp->obj); } From d3c1b0b9218f3106736897044cefeb8c6c926279 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:16:35 +0900 Subject: [PATCH 049/144] Use rb_float_convert_to_BigDecimal in GetVpValueWIthPrec --- ext/bigdecimal/bigdecimal.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 5defd0f1..51946154 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,6 +199,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -208,27 +209,14 @@ GetVpValueWithPrec(VALUE v, long prec, int must) VALUE num, bg; char szD[128]; VALUE orig = Qundef; - double d; again: switch(TYPE(v)) { - case T_FLOAT: - if (prec < 0) goto unable_to_coerce_without_prec; - if (prec > (long)DBLE_FIG) goto SomeOneMayDoIt; - d = RFLOAT_VALUE(v); - if (!isfinite(d)) { - pv = VpCreateRbObject(1, NULL, true); - VpDtoV(pv, d); - return pv; - } - if (d != 0.0) { - v = rb_funcall(v, id_to_r, 0); - goto again; - } - if (1/d < 0.0) { - return VpCreateRbObject(prec, "-0", true); - } - return VpCreateRbObject(prec, "0", true); + case T_FLOAT: { + VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } case T_RATIONAL: if (prec < 0) goto unable_to_coerce_without_prec; From 96c9ebd886da2880a9a44124d75d6044ed9d53b0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:19:52 +0900 Subject: [PATCH 050/144] Add assertions for checking the argument types --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 51946154..ef17882d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2799,6 +2799,8 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { + assert(RB_FLOAT_TYPE_P(val)); + double d = RFLOAT_VALUE(val); if (!isfinite(d)) { @@ -2838,6 +2840,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { + assert(RB_TYPE_P(val, T_RATIONAL)); + if (digs == SIZE_MAX) { if (!raise_exception) return Qnil; From b4f470da61a23f7863e2ada0edb097e772482cab Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 15:28:08 +0900 Subject: [PATCH 051/144] Stop using GetVpValueWithPrec in rb_rational_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ef17882d..f440b425 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2849,8 +2849,10 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) "can't omit precision for a %"PRIsVALUE".", CLASS_OF(val)); } - Real *vp = GetVpValueWithPrec(val, digs, 1); - return check_exception(vp->obj); + + VALUE num = rb_inum_convert_to_BigDecimal(rb_rational_num(val), 0, raise_exception); + VALUE d = BigDecimal_div2(num, rb_rational_den(val), SIZET2NUM(digs)); + return d; } static VALUE From 44a78df8668e0bacb33653591fd65f88938bac78 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:24:40 +0900 Subject: [PATCH 052/144] Use rb_rational_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f440b425..460456d9 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -200,17 +200,16 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { ENTER(1); Real *pv; - VALUE num, bg; + VALUE bg; char szD[128]; - VALUE orig = Qundef; -again: switch(TYPE(v)) { case T_FLOAT: { VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); @@ -218,20 +217,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) return pv; } - case T_RATIONAL: - if (prec < 0) goto unable_to_coerce_without_prec; - - if (orig == Qundef ? (orig = v, 1) : orig != v) { - num = rb_rational_num(v); - pv = GetVpValueWithPrec(num, -1, must); - if (pv == NULL) goto SomeOneMayDoIt; - - v = BigDecimal_div2(VpCheckGetValue(pv), rb_rational_den(v), LONG2NUM(prec)); - goto again; - } - - v = orig; - goto SomeOneMayDoIt; + case T_RATIONAL: { + VALUE obj = rb_rational_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } case T_DATA: if (is_kind_of_BigDecimal(v)) { @@ -268,14 +258,6 @@ GetVpValueWithPrec(VALUE v, long prec, int must) cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); } return NULL; /* NULL means to coerce */ - -unable_to_coerce_without_prec: - if (must) { - rb_raise(rb_eArgError, - "%"PRIsVALUE" can't be coerced into BigDecimal without a precision", - RB_OBJ_CLASSNAME(v)); - } - return NULL; } static Real* @@ -2794,8 +2776,6 @@ rb_inum_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_e } } -static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); - static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { From 034fd2b25e9ecd99bdbbd4483f352854286ada53 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:31:23 +0900 Subject: [PATCH 053/144] Stop using GetVpValue in rb_big_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 460456d9..4d8bb071 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2759,7 +2759,10 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex } #endif else { - Real *vp = GetVpValue(val, 1); + VALUE str = rb_big2str(val, 10); + Real *vp = VpCreateRbObject(RSTRING_LEN(str) + BASE_FIG + 1, + RSTRING_PTR(str), true); + RB_GC_GUARD(str); return check_exception(vp->obj); } } From a4a862c9179361832460f9d962e50a0ffb130e2e Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 17:34:54 +0900 Subject: [PATCH 054/144] Use rb_inum_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4d8bb071..292514a5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,16 +199,14 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - ENTER(1); Real *pv; - VALUE bg; - char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -234,8 +232,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) break; case T_FIXNUM: - sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + case T_BIGNUM: { + VALUE obj = rb_inum_convert_to_BigDecimal(v, prec, must); + TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); + return pv; + } #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -244,11 +245,6 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ - case T_BIGNUM: - bg = rb_big2str(v, 10); - PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } From 9d81e7d4e246f15ebdb3717e9189b615e93b8c83 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 21:11:23 +0900 Subject: [PATCH 055/144] WIP: 0.01.to_d --- test/bigdecimal/test_bigdecimal.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 12b25606..6316a204 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -146,6 +146,7 @@ def test_BigDecimal_with_rational def test_BigDecimal_with_float assert_equal(BigDecimal("0.1235"), BigDecimal(0.1234567, 4)) assert_equal(BigDecimal("-0.1235"), BigDecimal(-0.1234567, 4)) + assert_equal(BigDecimal("0.01"), BigDecimal(0.01, Float::DIG + 1)) assert_raise_with_message(ArgumentError, "can't omit precision for a Float.") { BigDecimal(4.2) } assert_raise(ArgumentError) { BigDecimal(0.1, Float::DIG + 2) } assert_nothing_raised { BigDecimal(0.1, Float::DIG + 1) } From f828d72cfb72e42c41c48dee1aef4469e022caeb Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 9 Jan 2021 22:21:24 +0900 Subject: [PATCH 056/144] Revert "Use rb_inum_convert_to_BigDecimal in GetVpValueWithPrec" This reverts commit a4a862c9179361832460f9d962e50a0ffb130e2e. --- ext/bigdecimal/bigdecimal.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 292514a5..4d8bb071 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -199,14 +199,16 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); -static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { + ENTER(1); Real *pv; + VALUE bg; + char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -232,11 +234,8 @@ GetVpValueWithPrec(VALUE v, long prec, int must) break; case T_FIXNUM: - case T_BIGNUM: { - VALUE obj = rb_inum_convert_to_BigDecimal(v, prec, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + sprintf(szD, "%ld", FIX2LONG(v)); + return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -245,6 +244,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ + case T_BIGNUM: + bg = rb_big2str(v, 10); + PUSH(bg); + return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); default: goto SomeOneMayDoIt; } From 44f26b9aa0a17ee71adfc8aec183d2d1251fe09f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:29:53 +0900 Subject: [PATCH 057/144] Use smallest local variable scope in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4d8bb071..c2f92bfe 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,10 +205,7 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - ENTER(1); Real *pv; - VALUE bg; - char szD[128]; switch(TYPE(v)) { case T_FLOAT: { @@ -233,9 +230,11 @@ GetVpValueWithPrec(VALUE v, long prec, int must) } break; - case T_FIXNUM: + case T_FIXNUM: { + char szD[128]; sprintf(szD, "%ld", FIX2LONG(v)); return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + } #ifdef ENABLE_NUMERIC_STRING case T_STRING: @@ -244,11 +243,14 @@ GetVpValueWithPrec(VALUE v, long prec, int must) RSTRING_PTR(v), true); #endif /* ENABLE_NUMERIC_STRING */ - case T_BIGNUM: - bg = rb_big2str(v, 10); - PUSH(bg); - return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); + case T_BIGNUM: { + VALUE bg = rb_big2str(v, 10); + pv = VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, + RSTRING_PTR(bg), true); + RB_GC_GUARD(bg); + return pv; + } + default: goto SomeOneMayDoIt; } From f047b2786f81cf9f79769fcbc0832e31150e41d9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:31:20 +0900 Subject: [PATCH 058/144] Avoid casting negative value to size_t --- ext/bigdecimal/bigdecimal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c2f92bfe..908a99b8 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,17 +205,18 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { + const size_t digs = prec < 0 ? SIZE_MAX : (long)prec; Real *pv; switch(TYPE(v)) { case T_FLOAT: { - VALUE obj = rb_float_convert_to_BigDecimal(v, prec, must); + VALUE obj = rb_float_convert_to_BigDecimal(v, digs, must); TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); return pv; } case T_RATIONAL: { - VALUE obj = rb_rational_convert_to_BigDecimal(v, prec, must); + VALUE obj = rb_rational_convert_to_BigDecimal(v, digs, must); TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); return pv; } From 2dad4d17b23d1521539845d07469a4fadb0746c0 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sun, 10 Jan 2021 08:37:35 +0900 Subject: [PATCH 059/144] Fix type name --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 908a99b8..d3206a57 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -205,7 +205,7 @@ static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { - const size_t digs = prec < 0 ? SIZE_MAX : (long)prec; + const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; Real *pv; switch(TYPE(v)) { From ac230a996e6cbfddd981c1476dc237ce5e6ab45d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 09:19:14 +0900 Subject: [PATCH 060/144] Add rb_cstr_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d3206a57..a4423af7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2842,18 +2842,24 @@ rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } static VALUE -rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception) { if (digs == SIZE_MAX) digs = 0; - const char *c_str = StringValueCStr(val); Real *vp = VpCreateRbObject(digs, c_str, raise_exception); if (!vp) return Qnil; return VpCheckGetValue(vp); } +static inline VALUE +rb_str_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) +{ + const char *c_str = StringValueCStr(val); + return rb_cstr_convert_to_BigDecimal(c_str, digs, raise_exception); +} + static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) { From 381ddf5ff6f5d3a99c24f160de5a714e306a67e9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 09:19:19 +0900 Subject: [PATCH 061/144] Use rb_cstr_convert_to_BigDecimal in GetVpValueWithPrec --- ext/bigdecimal/bigdecimal.c | 53 +++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a4423af7..482bc343 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -201,61 +201,58 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *cstr, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) { const size_t digs = prec < 0 ? SIZE_MAX : (size_t)prec; - Real *pv; switch(TYPE(v)) { - case T_FLOAT: { - VALUE obj = rb_float_convert_to_BigDecimal(v, digs, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + case T_FLOAT: + v = rb_float_convert_to_BigDecimal(v, digs, must); + break; - case T_RATIONAL: { - VALUE obj = rb_rational_convert_to_BigDecimal(v, digs, must); - TypedData_Get_Struct(obj, Real, &BigDecimal_data_type, pv); - return pv; - } + case T_RATIONAL: + v = rb_rational_convert_to_BigDecimal(v, digs, must); + break; case T_DATA: - if (is_kind_of_BigDecimal(v)) { - pv = DATA_PTR(v); - return pv; - } - else { - goto SomeOneMayDoIt; - } - break; + if (!is_kind_of_BigDecimal(v)) { + goto SomeOneMayDoIt; + } + break; case T_FIXNUM: { char szD[128]; - sprintf(szD, "%ld", FIX2LONG(v)); - return VpCreateRbObject(VpBaseFig() * 2 + 1, szD, true); + sprintf(szD, "%ld", FIX2LONG(v)); + v = rb_cstr_convert_to_BigDecimal(szD, VpBaseFig() * 2 + 1, must); + break; } #ifdef ENABLE_NUMERIC_STRING - case T_STRING: - StringValueCStr(v); - return VpCreateRbObject(RSTRING_LEN(v) + VpBaseFig() + 1, - RSTRING_PTR(v), true); + case T_STRING: { + const char *c_str = StringValueCStr(v); + v = rb_cstr_convert_to_BigDecimal(c_str, RSTRING_LEN(v) + VpBaseFig() + 1, must); + break; + } #endif /* ENABLE_NUMERIC_STRING */ case T_BIGNUM: { VALUE bg = rb_big2str(v, 10); - pv = VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, - RSTRING_PTR(bg), true); + v = rb_cstr_convert_to_BigDecimal(RSTRING_PTR(bg), RSTRING_LEN(bg) + VpBaseFig() + 1, must); RB_GC_GUARD(bg); - return pv; + break; } default: goto SomeOneMayDoIt; } + Real *vp; + TypedData_Get_Struct(v, Real, &BigDecimal_data_type, vp); + return vp; + SomeOneMayDoIt: if (must) { cannot_be_coerced_into_BigDecimal(rb_eTypeError, v); From 14e53ed7f67c51265f13b014c94c51f0789ae55c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 16:54:36 +0900 Subject: [PATCH 062/144] Fix length calculation in rb_uint64_convert_to_BigDecimal --- ext/bigdecimal/bigdecimal.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 482bc343..395c12bb 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2697,8 +2697,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len10 = ceil(LOG10_2 * bit_length(uval)); - size_t len = roomof(len10, BASE_FIG); + const size_t len = (size_t)ceil(log10(uval) / BASE_FIG); vp = VpAllocReal(len); vp->MaxPrec = len; From d163f170a4b9c475be46791311a0ec859c497194 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:56:54 +0900 Subject: [PATCH 063/144] Fix exception message raised in Kernel.BigDecimal --- ext/bigdecimal/bigdecimal.c | 29 +++++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 14 +++++++++++++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 395c12bb..6dd0850b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -201,7 +201,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); -static VALUE rb_cstr_convert_to_BigDecimal(const char *cstr, size_t digs, int raise_exception); +static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -2863,10 +2863,15 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) case Qnil: case Qtrue: case Qfalse: - if (!raise_exception) - return Qnil; - rb_raise(rb_eTypeError, - "can't convert %"PRIsVALUE" into BigDecimal", val); + if (raise_exception) { + const char *cname = NIL_P(val) ? "nil" : + val == Qtrue ? "true" : + val == Qfalse ? "false" : + NULL; + rb_raise(rb_eTypeError, + "can't convert %s into BigDecimal", cname); + } + return Qnil; default: break; @@ -2905,13 +2910,17 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) else if (RB_TYPE_P(val, T_STRING)) { return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } + /* TODO: chheck to_d */ /* TODO: chheck to_int */ - if (!raise_exception) { - VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); - if (NIL_P(str)) - return Qnil; - val = str; + + VALUE str = rb_check_convert_type(val, T_STRING, "String", "to_str"); + if (!RB_TYPE_P(str, T_STRING)) { + if (raise_exception) { + rb_raise(rb_eTypeError, + "can't convert %"PRIsVALUE" into BigDecimal", rb_obj_class(val)); + } + return Qnil; } return rb_str_convert_to_BigDecimal(val, digs, raise_exception); } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 6316a204..4c908d25 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -229,9 +229,18 @@ def test_BigDecimal_with_exception_keyword # assert_nothing_raised(RangeError) { # assert_equal(nil, BigDecimal(1i, exception: false)) # } - assert_raise(TypeError) { + assert_raise_with_message(TypeError, "can't convert nil into BigDecimal") { BigDecimal(nil, exception: true) } + assert_raise_with_message(TypeError, "can't convert true into BigDecimal") { + BigDecimal(true, exception: true) + } + assert_raise_with_message(TypeError, "can't convert false into BigDecimal") { + BigDecimal(false, exception: true) + } + assert_raise_with_message(TypeError, "can't convert Object into BigDecimal") { + BigDecimal(Object.new, exception: true) + } assert_nothing_raised(TypeError) { assert_equal(nil, BigDecimal(nil, exception: false)) } @@ -241,6 +250,9 @@ def test_BigDecimal_with_exception_keyword assert_nothing_raised(TypeError) { assert_equal(nil, BigDecimal(Object.new, exception: false)) } + assert_nothing_raised(TypeError) { + assert_equal(nil, BigDecimal(Object.new, exception: false)) + } # TODO: support to_d # assert_nothing_raised(TypeError) { # o = Object.new From 95c201f2d36eee4a2cfe832771fa47333a2d26fc Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:51:29 +0900 Subject: [PATCH 064/144] Use pre-allocated objects for special values --- ext/bigdecimal/bigdecimal.c | 115 +++++++++++++++++++++++------ test/bigdecimal/test_bigdecimal.rb | 8 +- 2 files changed, 101 insertions(+), 22 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6dd0850b..40c4473f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -127,6 +127,12 @@ static int VPrint(FILE *fp,const char *cntl_chr,Real *a); * **** BigDecimal part **** */ +static VALUE BigDecimal_nan(void); +static VALUE BigDecimal_positive_infinity(void); +static VALUE BigDecimal_negative_infinity(void); +static VALUE BigDecimal_positive_zero(void); +static VALUE BigDecimal_negative_zero(void); + static void BigDecimal_delete(void *pv) { @@ -2785,10 +2791,27 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) double d = RFLOAT_VALUE(val); - if (!isfinite(d)) { - Real *vp = VpCreateRbObject(1, NULL, true); /* vp->obj is allocated */ - VpDtoV(vp, d); - return check_exception(vp->obj); + if (isnan(d)) { + VALUE obj = BigDecimal_nan(); + return check_exception(obj); + } + else if (isinf(d)) { + VALUE obj; + if (d > 0) { + obj = BigDecimal_positive_infinity(); + } + else { + obj = BigDecimal_negative_infinity(); + } + return check_exception(obj); + } + else if (d == 0.0) { + if (1/d < 0.0) { + return BigDecimal_negative_zero(); + } + else { + return BigDecimal_positive_zero(); + } } if (digs == SIZE_MAX) { @@ -2804,19 +2827,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - if (d != 0.0) { - val = rb_funcall(val, id_to_r, 0); - return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); - } - - Real *vp; - if (1/d < 0.0) { - vp = VpCreateRbObject(digs, "-0", true); - } - else { - vp = VpCreateRbObject(digs, "0", true); - } - return check_exception(vp->obj); + val = rb_funcall(val, id_to_r, 0); + return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); } static VALUE @@ -3426,6 +3438,46 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) return y; } +static VALUE BIGDECIMAL_NAN = Qnil; + +static VALUE +BigDecimal_nan(void) +{ + return BIGDECIMAL_NAN; +} + +static VALUE BIGDECIMAL_POSITIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_positive_infinity(void) +{ + return BIGDECIMAL_POSITIVE_INFINITY; +} + +static VALUE BIGDECIMAL_NEGATIVE_INFINITY = Qnil; + +static VALUE +BigDecimal_negative_infinity(void) +{ + return BIGDECIMAL_NEGATIVE_INFINITY; +} + +static VALUE BIGDECIMAL_POSITIVE_ZERO = Qnil; + +static VALUE +BigDecimal_positive_zero(void) +{ + return BIGDECIMAL_POSITIVE_ZERO; +} + +static VALUE BIGDECIMAL_NEGATIVE_ZERO = Qnil; + +static VALUE +BigDecimal_negative_zero(void) +{ + return BIGDECIMAL_NEGATIVE_ZERO; +} + /* Document-class: BigDecimal * BigDecimal provides arbitrary-precision floating point decimal arithmetic. * @@ -3697,13 +3749,34 @@ Init_bigdecimal(void) /* -3: Indicates that a value is negative and infinite. See BigDecimal.sign. */ rb_define_const(rb_cBigDecimal, "SIGN_NEGATIVE_INFINITE", INT2FIX(VP_SIGN_NEGATIVE_INFINITE)); - arg = rb_str_new2("+Infinity"); + /* Positive zero value. */ + arg = rb_str_new2("+0"); + BIGDECIMAL_POSITIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_ZERO); + + /* Negative zero value. */ + arg = rb_str_new2("-0"); + BIGDECIMAL_NEGATIVE_ZERO = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_ZERO); + /* Positive infinity value. */ - rb_define_const(rb_cBigDecimal, "INFINITY", f_BigDecimal(1, &arg, rb_cBigDecimal)); - arg = rb_str_new2("NaN"); + arg = rb_str_new2("+Infinity"); + BIGDECIMAL_POSITIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_POSITIVE_INFINITY); + + /* Negative infinity value. */ + arg = rb_str_new2("-Infinity"); + BIGDECIMAL_NEGATIVE_INFINITY = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NEGATIVE_INFINITY); + /* 'Not a Number' value. */ - rb_define_const(rb_cBigDecimal, "NAN", f_BigDecimal(1, &arg, rb_cBigDecimal)); + arg = rb_str_new2("NaN"); + BIGDECIMAL_NAN = f_BigDecimal(1, &arg, rb_cBigDecimal); + rb_gc_register_mark_object(BIGDECIMAL_NAN); + /* Special value constants */ + rb_define_const(rb_cBigDecimal, "INFINITY", BIGDECIMAL_POSITIVE_INFINITY); + rb_define_const(rb_cBigDecimal, "NAN", BIGDECIMAL_NAN); /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 4c908d25..3d3a750a 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -151,17 +151,23 @@ def test_BigDecimal_with_float assert_raise(ArgumentError) { BigDecimal(0.1, Float::DIG + 2) } assert_nothing_raised { BigDecimal(0.1, Float::DIG + 1) } + assert_same(BigDecimal(0.0), BigDecimal(0.0)) + assert_same(BigDecimal(-0.0), BigDecimal(-0.0)) + bug9214 = '[ruby-core:58858]' - assert_equal(BigDecimal(-0.0, Float::DIG).sign, -1, bug9214) + assert_equal(BigDecimal(-0.0).sign, -1, bug9214) BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) assert_nan(BigDecimal(Float::NAN)) + assert_same(BigDecimal(Float::NAN), BigDecimal(Float::NAN)) end BigDecimal.save_exception_mode do BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_infinite(BigDecimal(Float::INFINITY)) + assert_same(BigDecimal(Float::INFINITY), BigDecimal(Float::INFINITY)) assert_negative_infinite(BigDecimal(-Float::INFINITY)) + assert_same(BigDecimal(-Float::INFINITY), BigDecimal(-Float::INFINITY)) end end From ff8eeeb0648b90b7c46d720138ec5c4e60ece60f Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 00:43:10 +0900 Subject: [PATCH 065/144] Should not pass the original object but the converted string --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 40c4473f..d1574c9d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2934,7 +2934,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } return Qnil; } - return rb_str_convert_to_BigDecimal(val, digs, raise_exception); + return rb_str_convert_to_BigDecimal(str, digs, raise_exception); } /* call-seq: From 9bfff57f9051f1461f933fff468eddef9f6dbeba Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:32:17 +0900 Subject: [PATCH 066/144] Stop using pry in development --- bigdecimal.gemspec | 2 +- bin/console | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 5dc35db7..86ea1801 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -41,5 +41,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", ">= 12.3.3" s.add_development_dependency "rake-compiler", ">= 0.9" s.add_development_dependency "minitest", "< 5.0.0" - s.add_development_dependency "pry" + s.add_development_dependency "irb" end diff --git a/bin/console b/bin/console index 43fde8bf..6d23f921 100755 --- a/bin/console +++ b/bin/console @@ -6,5 +6,5 @@ require "bigdecimal" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. -require "pry" -Pry.start +require "irb" +IRB.start From 41ee358af804b2ced5f0555fce96e53adb6ba5fd Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:36:25 +0900 Subject: [PATCH 067/144] CI: Limit events --- .github/workflows/benchmark.yml | 10 ++++++++-- .github/workflows/ci.yml | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 8c9aa8d8..5ce8fde8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,8 +1,14 @@ name: Benchmarking on: -- push -- pull_request + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened jobs: host: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 106b8040..985e0ec8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,14 @@ name: CI on: -- push -- pull_request + push: + branches: + - master + pull_request: + types: + - opened + - synchronize + - reopened jobs: host: From 5bdaedd53097e85bbe51387209f1f47aba13387b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 12 Jan 2021 22:58:17 +0900 Subject: [PATCH 068/144] Optimize rb_float_convert_to_BigDecimal by using dtoa --- ext/bigdecimal/bigdecimal.c | 117 +- ext/bigdecimal/bigdecimal.h | 3 +- ext/bigdecimal/extconf.rb | 1 + ext/bigdecimal/missing.c | 17 + ext/bigdecimal/missing.h | 3 + ext/bigdecimal/missing/dtoa.c | 3462 +++++++++++++++++++++++++++++++++ 6 files changed, 3599 insertions(+), 4 deletions(-) create mode 100644 ext/bigdecimal/missing.c create mode 100644 ext/bigdecimal/missing/dtoa.c diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d1574c9d..a2f78063 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -74,7 +74,7 @@ static ID id_half; #define BASE1 (BASE/10) #ifndef DBLE_FIG -#define DBLE_FIG rmpd_double_figures() /* figure of double */ +#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */ #endif #define LOG10_2 0.3010299956639812 @@ -205,6 +205,7 @@ cannot_be_coerced_into_BigDecimal(VALUE exc_class, VALUE v) } static inline VALUE BigDecimal_div2(VALUE, VALUE, VALUE); +static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); @@ -2827,8 +2828,118 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) rb_raise(rb_eArgError, "precision too large."); } - val = rb_funcall(val, id_to_r, 0); - return rb_rational_convert_to_BigDecimal(val, digs, raise_exception); + /* Use the same logic in flo_to_s to convert a float to a decimal string */ + char buf[DBLE_FIG + BASE_FIG + 2 + 1]; + int decpt, negative_p; + char *e; + char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + int len10 = (int)(e - p); + if (len10 >= (int)sizeof(buf)) + len10 = (int)sizeof(buf) - 1; + memcpy(buf, p, len10); + xfree(p); + + VALUE inum; + size_t RB_UNUSED_VAR(prec) = 0; + size_t exp = 0; + if (decpt > 0) { + if (decpt < len10) { + /* + * len10 |---------------| + * : |-------| frac_len10 = len10 - decpt + * decpt |-------| |--| ntz10 = BASE_FIG - frac_len10 % BASE_FIG + * : : : + * 00 dd dddd.dddd dd 00 + * prec |-----.----.----.-----| prec = exp + roomof(frac_len, BASE_FIG) + * exp |-----.----| exp = roomof(decpt, BASE_FIG) + */ + const size_t frac_len10 = len10 - decpt; + const size_t ntz10 = BASE_FIG - frac_len10 % BASE_FIG; + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = roomof(decpt, BASE_FIG); + prec = exp + roomof(frac_len10, BASE_FIG); + } + else { + /* + * decpt |-----------------------| + * len10 |----------| : + * : |------------| exp10 + * : : : + * 00 dd dddd dd 00 0000 0000.0 + * : : : : + * : |--| ntz10 = exp10 % BASE_FIG + * prec |-----.----.-----| : + * : |----.----| exp10 / BASE_FIG + * exp |-----.----.-----.----.----| + */ + const size_t exp10 = decpt - len10; + const size_t ntz10 = exp10 % BASE_FIG; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + prec = roomof(len10 + ntz10, BASE_FIG); + exp = prec + exp10 / BASE_FIG; + } + } + else if (decpt == 0) { + /* + * len10 |------------| + * : : + * 0.dddd dddd dd 00 + * : : : + * : |--| ntz10 = prec * BASE_FIG - len10 + * prec |----.----.-----| roomof(len10, BASE_FIG) + */ + prec = roomof(len10, BASE_FIG); + const size_t ntz10 = prec * BASE_FIG - len10; + + memset(buf + len10, '0', ntz10); + buf[len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + } + else { + /* + * len10 |---------------| + * : : + * decpt |-------| |--| ntz10 = prec * BASE_FIG - nlz10 - len10 + * : : : + * 0.0000 00 dd dddd dddd dd 00 + * : : : + * nlz10 |--| : decpt % BASE_FIG + * prec |-----.----.----.-----| roomof(decpt + len10, BASE_FIG) - exp + * exp |----| decpt / BASE_FIG + */ + decpt = -decpt; + + const size_t nlz10 = decpt % BASE_FIG; + exp = decpt / BASE_FIG; + prec = roomof(decpt + len10, BASE_FIG) - exp; + const size_t ntz10 = prec * BASE_FIG - nlz10 - len10; + + if (nlz10 > 0) { + memmove(buf + nlz10, buf, len10); + memset(buf, '0', nlz10); + } + memset(buf + nlz10 + len10, '0', ntz10); + buf[nlz10 + len10 + ntz10] = '\0'; + inum = rb_cstr_to_inum(buf, 10, false); + + exp = -exp; + } + + VALUE bd = rb_inum_convert_to_BigDecimal(inum, SIZE_MAX, raise_exception); + Real *vp; + TypedData_Get_Struct(bd, Real, &BigDecimal_data_type, vp); + assert(vp->Prec == prec); + vp->exponent = exp; + + if (negative_p) VpSetSign(vp, -1); + return bd; } static VALUE diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 6b6ac216..5f343db6 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -77,6 +77,7 @@ extern VALUE rb_cBigDecimal; # define RMPD_BASE ((DECDIG)100U) #endif +#define RMPD_DOUBLE_FIGURES (1+DBL_DIG) /* * NaN & Infinity @@ -175,7 +176,7 @@ rmpd_base_value(void) { return RMPD_BASE; } static inline size_t rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } static inline size_t -rmpd_double_figures(void) { return 1+DBL_DIG; } +rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; } #define VpBaseFig() rmpd_component_figures() #define VpDblFig() rmpd_double_figures() diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index d5140e8a..c92aacb3 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -66,6 +66,7 @@ def have_builtin_func(name, check_expr, opt = "", &b) have_func("finite", "math.h") have_func("isfinite", "math.h") +have_header("ruby/atomic.h") have_header("ruby/internal/has/builtin.h") have_header("ruby/internal/static_assert.h") diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c new file mode 100644 index 00000000..b0bc6eea --- /dev/null +++ b/ext/bigdecimal/missing.c @@ -0,0 +1,17 @@ +#include + +#ifdef HAVE_RUBY_ATOMIC_H +# include +#endif + +#ifdef RUBY_ATOMIC_PTR_CAS +# define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) +#endif + +#undef strtod +#define strtod BigDecimal_strtod +#undef dtoa +#define dtoa BigDecimal_dtoa +#undef hdtoa +#define hdtoa BigDecimal_hdtoa +#include "missing/dtoa.c" diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index aa056c32..11b58c09 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -117,6 +117,9 @@ finite(double) # endif #endif +/* dtoa */ +char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve); + /* rational */ #ifndef HAVE_RB_RATIONAL_NUM diff --git a/ext/bigdecimal/missing/dtoa.c b/ext/bigdecimal/missing/dtoa.c new file mode 100644 index 00000000..41b0a221 --- /dev/null +++ b/ext/bigdecimal/missing/dtoa.c @@ -0,0 +1,3462 @@ +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +/* Please send bug reports to David M. Gay (dmg at acm dot org, + * with " at " changed at "@" and " dot " changed to "."). */ + +/* On a machine with IEEE extended-precision registers, it is + * necessary to specify double-precision (53-bit) rounding precision + * before invoking strtod or dtoa. If the machine uses (the equivalent + * of) Intel 80x87 arithmetic, the call + * _control87(PC_53, MCW_PC); + * does this with many compilers. Whether this or another call is + * appropriate depends on the compiler; for this to work, it may be + * necessary to #include "float.h" or another system-dependent header + * file. + */ + +/* strtod for IEEE-, VAX-, and IBM-arithmetic machines. + * + * This strtod returns a nearest machine number to the input decimal + * string (or sets errno to ERANGE). With IEEE arithmetic, ties are + * broken by the IEEE round-even rule. Otherwise ties are broken by + * biased rounding (add half and chop). + * + * Inspired loosely by William D. Clinger's paper "How to Read Floating + * Point Numbers Accurately" [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * + * 1. We only require IEEE, IBM, or VAX double-precision + * arithmetic (not IEEE double-extended). + * 2. We get by with floating-point arithmetic in a case that + * Clinger missed -- when we're computing d * 10^n + * for a small integer d and the integer n is not too + * much larger than 22 (the maximum integer k for which + * we can represent 10^k exactly), we may be able to + * compute (d*10^k) * 10^(e-k) with just one roundoff. + * 3. Rather than a bit-at-a-time adjustment of the binary + * result in the hard case, we use floating-point + * arithmetic to determine the adjustment to within + * one bit; only in really hard cases do we need to + * compute a second residual. + * 4. Because of 3., we don't need a large table of powers of 10 + * for ten-to-e (just some small tables, e.g. of 10^k + * for 0 <= k <= 22). + */ + +/* + * #define IEEE_LITTLE_ENDIAN for IEEE-arithmetic machines where the least + * significant byte has the lowest address. + * #define IEEE_BIG_ENDIAN for IEEE-arithmetic machines where the most + * significant byte has the lowest address. + * #define Long int on machines with 32-bit ints and 64-bit longs. + * #define IBM for IBM mainframe-style floating-point arithmetic. + * #define VAX for VAX-style floating-point arithmetic (D_floating). + * #define No_leftright to omit left-right logic in fast floating-point + * computation of dtoa. + * #define Honor_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and strtod and dtoa should round accordingly. + * #define Check_FLT_ROUNDS if FLT_ROUNDS can assume the values 2 or 3 + * and Honor_FLT_ROUNDS is not #defined. + * #define RND_PRODQUOT to use rnd_prod and rnd_quot (assembly routines + * that use extended-precision instructions to compute rounded + * products and quotients) with IBM. + * #define ROUND_BIASED for IEEE-format with biased rounding. + * #define Inaccurate_Divide for IEEE-format with correctly rounded + * products but inaccurate quotients, e.g., for Intel i860. + * #define NO_LONG_LONG on machines that do not have a "long long" + * integer type (of >= 64 bits). On such machines, you can + * #define Just_16 to store 16 bits per 32-bit Long when doing + * high-precision integer arithmetic. Whether this speeds things + * up or slows things down depends on the machine and the number + * being converted. If long long is available and the name is + * something other than "long long", #define Llong to be the name, + * and if "unsigned Llong" does not work as an unsigned version of + * Llong, #define #ULLong to be the corresponding unsigned type. + * #define KR_headers for old-style C function headers. + * #define Bad_float_h if your system lacks a float.h or if it does not + * define some or all of DBL_DIG, DBL_MAX_10_EXP, DBL_MAX_EXP, + * FLT_RADIX, FLT_ROUNDS, and DBL_MAX. + * #define MALLOC your_malloc, where your_malloc(n) acts like malloc(n) + * if memory is available and otherwise does something you deem + * appropriate. If MALLOC is undefined, malloc will be invoked + * directly -- and assumed always to succeed. + * #define Omit_Private_Memory to omit logic (added Jan. 1998) for making + * memory allocations from a private pool of memory when possible. + * When used, the private pool is PRIVATE_MEM bytes long: 2304 bytes, + * unless #defined to be a different length. This default length + * suffices to get rid of MALLOC calls except for unusual cases, + * such as decimal-to-binary conversion of a very long string of + * digits. The longest string dtoa can return is about 751 bytes + * long. For conversions by strtod of strings of 800 digits and + * all dtoa conversions in single-threaded executions with 8-byte + * pointers, PRIVATE_MEM >= 7400 appears to suffice; with 4-byte + * pointers, PRIVATE_MEM >= 7112 appears adequate. + * #define INFNAN_CHECK on IEEE systems to cause strtod to check for + * Infinity and NaN (case insensitively). On some systems (e.g., + * some HP systems), it may be necessary to #define NAN_WORD0 + * appropriately -- to the most significant word of a quiet NaN. + * (On HP Series 700/800 machines, -DNAN_WORD0=0x7ff40000 works.) + * When INFNAN_CHECK is #defined and No_Hex_NaN is not #defined, + * strtod also accepts (case insensitively) strings of the form + * NaN(x), where x is a string of hexadecimal digits and spaces; + * if there is only one string of hexadecimal digits, it is taken + * for the 52 fraction bits of the resulting NaN; if there are two + * or more strings of hex digits, the first is for the high 20 bits, + * the second and subsequent for the low 32 bits, with intervening + * white space ignored; but if this results in none of the 52 + * fraction bits being on (an IEEE Infinity symbol), then NAN_WORD0 + * and NAN_WORD1 are used instead. + * #define MULTIPLE_THREADS if the system offers preemptively scheduled + * multiple threads. In this case, you must provide (or suitably + * #define) two locks, acquired by ACQUIRE_DTOA_LOCK(n) and freed + * by FREE_DTOA_LOCK(n) for n = 0 or 1. (The second lock, accessed + * in pow5mult, ensures lazy evaluation of only one copy of high + * powers of 5; omitting this lock would introduce a small + * probability of wasting memory, but would otherwise be harmless.) + * You must also invoke freedtoa(s) to free the value s returned by + * dtoa. You may do so whether or not MULTIPLE_THREADS is #defined. + * #define NO_IEEE_Scale to disable new (Feb. 1997) logic in strtod that + * avoids underflows on inputs whose result does not underflow. + * If you #define NO_IEEE_Scale on a machine that uses IEEE-format + * floating-point numbers and flushes underflows to zero rather + * than implementing gradual underflow, then you must also #define + * Sudden_Underflow. + * #define YES_ALIAS to permit aliasing certain double values with + * arrays of ULongs. This leads to slightly better code with + * some compilers and was always used prior to 19990916, but it + * is not strictly legal and can cause trouble with aggressively + * optimizing compilers (e.g., gcc 2.95.1 under -O2). + * #define USE_LOCALE to use the current locale's decimal_point value. + * #define SET_INEXACT if IEEE arithmetic is being used and extra + * computation should be done to set the inexact flag when the + * result is inexact and avoid setting inexact when the result + * is exact. In this case, dtoa.c must be compiled in + * an environment, perhaps provided by #include "dtoa.c" in a + * suitable wrapper, that defines two functions, + * int get_inexact(void); + * void clear_inexact(void); + * such that get_inexact() returns a nonzero value if the + * inexact bit is already set, and clear_inexact() sets the + * inexact bit to 0. When SET_INEXACT is #defined, strtod + * also does extra computations to set the underflow and overflow + * flags when appropriate (i.e., when the result is tiny and + * inexact or when it is a numeric value rounded to +-infinity). + * #define NO_ERRNO if strtod should not assign errno = ERANGE when + * the result overflows to +-Infinity or underflows to 0. + */ + +#ifdef WORDS_BIGENDIAN +#define IEEE_BIG_ENDIAN +#else +#define IEEE_LITTLE_ENDIAN +#endif + +#ifdef __vax__ +#define VAX +#undef IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#if defined(__arm__) && !defined(__VFP_FP__) +#define IEEE_BIG_ENDIAN +#undef IEEE_LITTLE_ENDIAN +#endif + +#undef Long +#undef ULong + +#include + +#if (INT_MAX >> 30) && !(INT_MAX >> 31) +#define Long int +#define ULong unsigned int +#elif (LONG_MAX >> 30) && !(LONG_MAX >> 31) +#define Long long int +#define ULong unsigned long int +#else +#error No 32bit integer +#endif + +#if HAVE_LONG_LONG +#define Llong LONG_LONG +#else +#define NO_LONG_LONG +#endif + +#ifdef DEBUG +#include +#define Bug(x) {fprintf(stderr, "%s\n", (x)); exit(EXIT_FAILURE);} +#endif + +#ifndef ISDIGIT +#include +#define ISDIGIT(c) isdigit(c) +#endif +#include +#include +#include + +#ifdef USE_LOCALE +#include +#endif + +#ifdef MALLOC +extern void *MALLOC(size_t); +#else +#define MALLOC xmalloc +#endif +#ifdef FREE +extern void FREE(void*); +#else +#define FREE xfree +#endif +#ifndef NO_SANITIZE +#define NO_SANITIZE(x, y) y +#endif + +#ifndef Omit_Private_Memory +#ifndef PRIVATE_MEM +#define PRIVATE_MEM 2304 +#endif +#define PRIVATE_mem ((PRIVATE_MEM+sizeof(double)-1)/sizeof(double)) +static double private_mem[PRIVATE_mem], *pmem_next = private_mem; +#endif + +#undef IEEE_Arith +#undef Avoid_Underflow +#ifdef IEEE_BIG_ENDIAN +#define IEEE_Arith +#endif +#ifdef IEEE_LITTLE_ENDIAN +#define IEEE_Arith +#endif + +#ifdef Bad_float_h + +#ifdef IEEE_Arith +#define DBL_DIG 15 +#define DBL_MAX_10_EXP 308 +#define DBL_MAX_EXP 1024 +#define FLT_RADIX 2 +#endif /*IEEE_Arith*/ + +#ifdef IBM +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 75 +#define DBL_MAX_EXP 63 +#define FLT_RADIX 16 +#define DBL_MAX 7.2370055773322621e+75 +#endif + +#ifdef VAX +#define DBL_DIG 16 +#define DBL_MAX_10_EXP 38 +#define DBL_MAX_EXP 127 +#define FLT_RADIX 2 +#define DBL_MAX 1.7014118346046923e+38 +#endif + +#ifndef LONG_MAX +#define LONG_MAX 2147483647 +#endif + +#else /* ifndef Bad_float_h */ +#include +#endif /* Bad_float_h */ + +#include + +#ifdef __cplusplus +extern "C" { +#if 0 +} /* satisfy cc-mode */ +#endif +#endif + +#ifndef hexdigit +static const char hexdigit[] = "0123456789abcdef0123456789ABCDEF"; +#endif + +#if defined(IEEE_LITTLE_ENDIAN) + defined(IEEE_BIG_ENDIAN) + defined(VAX) + defined(IBM) != 1 +Exactly one of IEEE_LITTLE_ENDIAN, IEEE_BIG_ENDIAN, VAX, or IBM should be defined. +#endif + +typedef union { double d; ULong L[2]; } U; + +#ifdef YES_ALIAS +typedef double double_u; +# define dval(x) (x) +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) (((ULong *)&(x))[1]) +# define word1(x) (((ULong *)&(x))[0]) +# else +# define word0(x) (((ULong *)&(x))[0]) +# define word1(x) (((ULong *)&(x))[1]) +# endif +#else +typedef U double_u; +# ifdef IEEE_LITTLE_ENDIAN +# define word0(x) ((x).L[1]) +# define word1(x) ((x).L[0]) +# else +# define word0(x) ((x).L[0]) +# define word1(x) ((x).L[1]) +# endif +# define dval(x) ((x).d) +#endif + +/* The following definition of Storeinc is appropriate for MIPS processors. + * An alternative that might be better on some machines is + * #define Storeinc(a,b,c) (*a++ = b << 16 | c & 0xffff) + */ +#if defined(IEEE_LITTLE_ENDIAN) + defined(VAX) + defined(__arm__) +#define Storeinc(a,b,c) (((unsigned short *)(a))[1] = (unsigned short)(b), \ +((unsigned short *)(a))[0] = (unsigned short)(c), (a)++) +#else +#define Storeinc(a,b,c) (((unsigned short *)(a))[0] = (unsigned short)(b), \ +((unsigned short *)(a))[1] = (unsigned short)(c), (a)++) +#endif + +/* #define P DBL_MANT_DIG */ +/* Ten_pmax = floor(P*log(2)/log(5)) */ +/* Bletch = (highest power of 2 < DBL_MAX_10_EXP) / 16 */ +/* Quick_max = floor((P-1)*log(FLT_RADIX)/log(10) - 1) */ +/* Int_max = floor(P*log(FLT_RADIX)/log(10) - 1) */ + +#ifdef IEEE_Arith +#define Exp_shift 20 +#define Exp_shift1 20 +#define Exp_msk1 0x100000 +#define Exp_msk11 0x100000 +#define Exp_mask 0x7ff00000 +#define P 53 +#define Bias 1023 +#define Emin (-1022) +#define Exp_1 0x3ff00000 +#define Exp_11 0x3ff00000 +#define Ebits 11 +#define Frac_mask 0xfffff +#define Frac_mask1 0xfffff +#define Ten_pmax 22 +#define Bletch 0x10 +#define Bndry_mask 0xfffff +#define Bndry_mask1 0xfffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 1 +#define Tiny0 0 +#define Tiny1 1 +#define Quick_max 14 +#define Int_max 14 +#ifndef NO_IEEE_Scale +#define Avoid_Underflow +#ifdef Flush_Denorm /* debugging option */ +#undef Sudden_Underflow +#endif +#endif + +#ifndef Flt_Rounds +#ifdef FLT_ROUNDS +#define Flt_Rounds FLT_ROUNDS +#else +#define Flt_Rounds 1 +#endif +#endif /*Flt_Rounds*/ + +#ifdef Honor_FLT_ROUNDS +#define Rounding rounding +#undef Check_FLT_ROUNDS +#define Check_FLT_ROUNDS +#else +#define Rounding Flt_Rounds +#endif + +#else /* ifndef IEEE_Arith */ +#undef Check_FLT_ROUNDS +#undef Honor_FLT_ROUNDS +#undef SET_INEXACT +#undef Sudden_Underflow +#define Sudden_Underflow +#ifdef IBM +#undef Flt_Rounds +#define Flt_Rounds 0 +#define Exp_shift 24 +#define Exp_shift1 24 +#define Exp_msk1 0x1000000 +#define Exp_msk11 0x1000000 +#define Exp_mask 0x7f000000 +#define P 14 +#define Bias 65 +#define Exp_1 0x41000000 +#define Exp_11 0x41000000 +#define Ebits 8 /* exponent has 7 bits, but 8 is the right value in b2d */ +#define Frac_mask 0xffffff +#define Frac_mask1 0xffffff +#define Bletch 4 +#define Ten_pmax 22 +#define Bndry_mask 0xefffff +#define Bndry_mask1 0xffffff +#define LSB 1 +#define Sign_bit 0x80000000 +#define Log2P 4 +#define Tiny0 0x100000 +#define Tiny1 0 +#define Quick_max 14 +#define Int_max 15 +#else /* VAX */ +#undef Flt_Rounds +#define Flt_Rounds 1 +#define Exp_shift 23 +#define Exp_shift1 7 +#define Exp_msk1 0x80 +#define Exp_msk11 0x800000 +#define Exp_mask 0x7f80 +#define P 56 +#define Bias 129 +#define Exp_1 0x40800000 +#define Exp_11 0x4080 +#define Ebits 8 +#define Frac_mask 0x7fffff +#define Frac_mask1 0xffff007f +#define Ten_pmax 24 +#define Bletch 2 +#define Bndry_mask 0xffff007f +#define Bndry_mask1 0xffff007f +#define LSB 0x10000 +#define Sign_bit 0x8000 +#define Log2P 1 +#define Tiny0 0x80 +#define Tiny1 0 +#define Quick_max 15 +#define Int_max 15 +#endif /* IBM, VAX */ +#endif /* IEEE_Arith */ + +#ifndef IEEE_Arith +#define ROUND_BIASED +#endif + +#ifdef RND_PRODQUOT +#define rounded_product(a,b) ((a) = rnd_prod((a), (b))) +#define rounded_quotient(a,b) ((a) = rnd_quot((a), (b))) +extern double rnd_prod(double, double), rnd_quot(double, double); +#else +#define rounded_product(a,b) ((a) *= (b)) +#define rounded_quotient(a,b) ((a) /= (b)) +#endif + +#define Big0 (Frac_mask1 | Exp_msk1*(DBL_MAX_EXP+Bias-1)) +#define Big1 0xffffffff + +#ifndef Pack_32 +#define Pack_32 +#endif + +#define FFFFFFFF 0xffffffffUL + +#ifdef NO_LONG_LONG +#undef ULLong +#ifdef Just_16 +#undef Pack_32 +/* When Pack_32 is not defined, we store 16 bits per 32-bit Long. + * This makes some inner loops simpler and sometimes saves work + * during multiplications, but it often seems to make things slightly + * slower. Hence the default is now to store 32 bits per Long. + */ +#endif +#else /* long long available */ +#ifndef Llong +#define Llong long long +#endif +#ifndef ULLong +#define ULLong unsigned Llong +#endif +#endif /* NO_LONG_LONG */ + +#define MULTIPLE_THREADS 1 + +#ifndef MULTIPLE_THREADS +#define ACQUIRE_DTOA_LOCK(n) /*nothing*/ +#define FREE_DTOA_LOCK(n) /*nothing*/ +#else +#define ACQUIRE_DTOA_LOCK(n) /*unused right now*/ +#define FREE_DTOA_LOCK(n) /*unused right now*/ +#endif + +#ifndef ATOMIC_PTR_CAS +#define ATOMIC_PTR_CAS(var, old, new) ((var) = (new), (old)) +#endif +#ifndef LIKELY +#define LIKELY(x) (x) +#endif +#ifndef UNLIKELY +#define UNLIKELY(x) (x) +#endif +#ifndef ASSUME +#define ASSUME(x) (void)(x) +#endif + +#define Kmax 15 + +struct Bigint { + struct Bigint *next; + int k, maxwds, sign, wds; + ULong x[1]; +}; + +typedef struct Bigint Bigint; + +static Bigint *freelist[Kmax+1]; + +static Bigint * +Balloc(int k) +{ + int x; + Bigint *rv; +#ifndef Omit_Private_Memory + size_t len; +#endif + + rv = 0; + ACQUIRE_DTOA_LOCK(0); + if (k <= Kmax) { + rv = freelist[k]; + while (rv) { + Bigint *rvn = rv; + rv = ATOMIC_PTR_CAS(freelist[k], rv, rv->next); + if (LIKELY(rvn == rv)) { + ASSUME(rv); + break; + } + } + } + if (!rv) { + x = 1 << k; +#ifdef Omit_Private_Memory + rv = (Bigint *)MALLOC(sizeof(Bigint) + (x-1)*sizeof(ULong)); +#else + len = (sizeof(Bigint) + (x-1)*sizeof(ULong) + sizeof(double) - 1) + /sizeof(double); + if (k <= Kmax) { + double *pnext = pmem_next; + while (pnext - private_mem + len <= PRIVATE_mem) { + double *p = pnext; + pnext = ATOMIC_PTR_CAS(pmem_next, pnext, pnext + len); + if (LIKELY(p == pnext)) { + rv = (Bigint*)pnext; + ASSUME(rv); + break; + } + } + } + if (!rv) + rv = (Bigint*)MALLOC(len*sizeof(double)); +#endif + rv->k = k; + rv->maxwds = x; + } + FREE_DTOA_LOCK(0); + rv->sign = rv->wds = 0; + return rv; +} + +static void +Bfree(Bigint *v) +{ + Bigint *vn; + if (v) { + if (v->k > Kmax) { + FREE(v); + return; + } + ACQUIRE_DTOA_LOCK(0); + do { + vn = v->next = freelist[v->k]; + } while (UNLIKELY(ATOMIC_PTR_CAS(freelist[v->k], vn, v) != vn)); + FREE_DTOA_LOCK(0); + } +} + +#define Bcopy(x,y) memcpy((char *)&(x)->sign, (char *)&(y)->sign, \ +(y)->wds*sizeof(Long) + 2*sizeof(int)) + +static Bigint * +multadd(Bigint *b, int m, int a) /* multiply by m and add a */ +{ + int i, wds; + ULong *x; +#ifdef ULLong + ULLong carry, y; +#else + ULong carry, y; +#ifdef Pack_32 + ULong xi, z; +#endif +#endif + Bigint *b1; + + wds = b->wds; + x = b->x; + i = 0; + carry = a; + do { +#ifdef ULLong + y = *x * (ULLong)m + carry; + carry = y >> 32; + *x++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + xi = *x; + y = (xi & 0xffff) * m + carry; + z = (xi >> 16) * m + (y >> 16); + carry = z >> 16; + *x++ = (z << 16) + (y & 0xffff); +#else + y = *x * m + carry; + carry = y >> 16; + *x++ = y & 0xffff; +#endif +#endif + } while (++i < wds); + if (carry) { + if (wds >= b->maxwds) { + b1 = Balloc(b->k+1); + Bcopy(b1, b); + Bfree(b); + b = b1; + } + b->x[wds++] = (ULong)carry; + b->wds = wds; + } + return b; +} + +static Bigint * +s2b(const char *s, int nd0, int nd, ULong y9) +{ + Bigint *b; + int i, k; + Long x, y; + + x = (nd + 8) / 9; + for (k = 0, y = 1; x > y; y <<= 1, k++) ; +#ifdef Pack_32 + b = Balloc(k); + b->x[0] = y9; + b->wds = 1; +#else + b = Balloc(k+1); + b->x[0] = y9 & 0xffff; + b->wds = (b->x[1] = y9 >> 16) ? 2 : 1; +#endif + + i = 9; + if (9 < nd0) { + s += 9; + do { + b = multadd(b, 10, *s++ - '0'); + } while (++i < nd0); + s++; + } + else + s += 10; + for (; i < nd; i++) + b = multadd(b, 10, *s++ - '0'); + return b; +} + +static int +hi0bits(register ULong x) +{ + register int k = 0; + + if (!(x & 0xffff0000)) { + k = 16; + x <<= 16; + } + if (!(x & 0xff000000)) { + k += 8; + x <<= 8; + } + if (!(x & 0xf0000000)) { + k += 4; + x <<= 4; + } + if (!(x & 0xc0000000)) { + k += 2; + x <<= 2; + } + if (!(x & 0x80000000)) { + k++; + if (!(x & 0x40000000)) + return 32; + } + return k; +} + +static int +lo0bits(ULong *y) +{ + register int k; + register ULong x = *y; + + if (x & 7) { + if (x & 1) + return 0; + if (x & 2) { + *y = x >> 1; + return 1; + } + *y = x >> 2; + return 2; + } + k = 0; + if (!(x & 0xffff)) { + k = 16; + x >>= 16; + } + if (!(x & 0xff)) { + k += 8; + x >>= 8; + } + if (!(x & 0xf)) { + k += 4; + x >>= 4; + } + if (!(x & 0x3)) { + k += 2; + x >>= 2; + } + if (!(x & 1)) { + k++; + x >>= 1; + if (!x) + return 32; + } + *y = x; + return k; +} + +static Bigint * +i2b(int i) +{ + Bigint *b; + + b = Balloc(1); + b->x[0] = i; + b->wds = 1; + return b; +} + +static Bigint * +mult(Bigint *a, Bigint *b) +{ + Bigint *c; + int k, wa, wb, wc; + ULong *x, *xa, *xae, *xb, *xbe, *xc, *xc0; + ULong y; +#ifdef ULLong + ULLong carry, z; +#else + ULong carry, z; +#ifdef Pack_32 + ULong z2; +#endif +#endif + + if (a->wds < b->wds) { + c = a; + a = b; + b = c; + } + k = a->k; + wa = a->wds; + wb = b->wds; + wc = wa + wb; + if (wc > a->maxwds) + k++; + c = Balloc(k); + for (x = c->x, xa = x + wc; x < xa; x++) + *x = 0; + xa = a->x; + xae = xa + wa; + xb = b->x; + xbe = xb + wb; + xc0 = c->x; +#ifdef ULLong + for (; xb < xbe; xc0++) { + if ((y = *xb++) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * (ULLong)y + *xc + carry; + carry = z >> 32; + *xc++ = (ULong)(z & FFFFFFFF); + } while (x < xae); + *xc = (ULong)carry; + } + } +#else +#ifdef Pack_32 + for (; xb < xbe; xb++, xc0++) { + if ((y = *xb & 0xffff) != 0) { + x = xa; + xc = xc0; + carry = 0; + do { + z = (*x & 0xffff) * y + (*xc & 0xffff) + carry; + carry = z >> 16; + z2 = (*x++ >> 16) * y + (*xc >> 16) + carry; + carry = z2 >> 16; + Storeinc(xc, z2, z); + } while (x < xae); + *xc = (ULong)carry; + } + if ((y = *xb >> 16) != 0) { + x = xa; + xc = xc0; + carry = 0; + z2 = *xc; + do { + z = (*x & 0xffff) * y + (*xc >> 16) + carry; + carry = z >> 16; + Storeinc(xc, z, z2); + z2 = (*x++ >> 16) * y + (*xc & 0xffff) + carry; + carry = z2 >> 16; + } while (x < xae); + *xc = z2; + } + } +#else + for (; xb < xbe; xc0++) { + if (y = *xb++) { + x = xa; + xc = xc0; + carry = 0; + do { + z = *x++ * y + *xc + carry; + carry = z >> 16; + *xc++ = z & 0xffff; + } while (x < xae); + *xc = (ULong)carry; + } + } +#endif +#endif + for (xc0 = c->x, xc = xc0 + wc; wc > 0 && !*--xc; --wc) ; + c->wds = wc; + return c; +} + +static Bigint *p5s; + +static Bigint * +pow5mult(Bigint *b, int k) +{ + Bigint *b1, *p5, *p51; + Bigint *p5tmp; + int i; + static const int p05[3] = { 5, 25, 125 }; + + if ((i = k & 3) != 0) + b = multadd(b, p05[i-1], 0); + + if (!(k >>= 2)) + return b; + if (!(p5 = p5s)) { + /* first time */ + ACQUIRE_DTOA_LOCK(1); + if (!(p5 = p5s)) { + p5 = i2b(625); + p5->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5s, NULL, p5); + if (UNLIKELY(p5tmp)) { + Bfree(p5); + p5 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + for (;;) { + if (k & 1) { + b1 = mult(b, p5); + Bfree(b); + b = b1; + } + if (!(k >>= 1)) + break; + if (!(p51 = p5->next)) { + ACQUIRE_DTOA_LOCK(1); + if (!(p51 = p5->next)) { + p51 = mult(p5,p5); + p51->next = 0; + p5tmp = ATOMIC_PTR_CAS(p5->next, NULL, p51); + if (UNLIKELY(p5tmp)) { + Bfree(p51); + p51 = p5tmp; + } + } + FREE_DTOA_LOCK(1); + } + p5 = p51; + } + return b; +} + +static Bigint * +lshift(Bigint *b, int k) +{ + int i, k1, n, n1; + Bigint *b1; + ULong *x, *x1, *xe, z; + +#ifdef Pack_32 + n = k >> 5; +#else + n = k >> 4; +#endif + k1 = b->k; + n1 = n + b->wds + 1; + for (i = b->maxwds; n1 > i; i <<= 1) + k1++; + b1 = Balloc(k1); + x1 = b1->x; + for (i = 0; i < n; i++) + *x1++ = 0; + x = b->x; + xe = x + b->wds; +#ifdef Pack_32 + if (k &= 0x1f) { + k1 = 32 - k; + z = 0; + do { + *x1++ = *x << k | z; + z = *x++ >> k1; + } while (x < xe); + if ((*x1 = z) != 0) + ++n1; + } +#else + if (k &= 0xf) { + k1 = 16 - k; + z = 0; + do { + *x1++ = *x << k & 0xffff | z; + z = *x++ >> k1; + } while (x < xe); + if (*x1 = z) + ++n1; + } +#endif + else + do { + *x1++ = *x++; + } while (x < xe); + b1->wds = n1 - 1; + Bfree(b); + return b1; +} + +static int +cmp(Bigint *a, Bigint *b) +{ + ULong *xa, *xa0, *xb, *xb0; + int i, j; + + i = a->wds; + j = b->wds; +#ifdef DEBUG + if (i > 1 && !a->x[i-1]) + Bug("cmp called with a->x[a->wds-1] == 0"); + if (j > 1 && !b->x[j-1]) + Bug("cmp called with b->x[b->wds-1] == 0"); +#endif + if (i -= j) + return i; + xa0 = a->x; + xa = xa0 + j; + xb0 = b->x; + xb = xb0 + j; + for (;;) { + if (*--xa != *--xb) + return *xa < *xb ? -1 : 1; + if (xa <= xa0) + break; + } + return 0; +} + +NO_SANITIZE("unsigned-integer-overflow", static Bigint * diff(Bigint *a, Bigint *b)); +static Bigint * +diff(Bigint *a, Bigint *b) +{ + Bigint *c; + int i, wa, wb; + ULong *xa, *xae, *xb, *xbe, *xc; +#ifdef ULLong + ULLong borrow, y; +#else + ULong borrow, y; +#ifdef Pack_32 + ULong z; +#endif +#endif + + i = cmp(a,b); + if (!i) { + c = Balloc(0); + c->wds = 1; + c->x[0] = 0; + return c; + } + if (i < 0) { + c = a; + a = b; + b = c; + i = 1; + } + else + i = 0; + c = Balloc(a->k); + c->sign = i; + wa = a->wds; + xa = a->x; + xae = xa + wa; + wb = b->wds; + xb = b->x; + xbe = xb + wb; + xc = c->x; + borrow = 0; +#ifdef ULLong + do { + y = (ULLong)*xa++ - *xb++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = y >> 32 & (ULong)1; + *xc++ = (ULong)(y & FFFFFFFF); + } +#else +#ifdef Pack_32 + do { + y = (*xa & 0xffff) - (*xb & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - (*xb++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } while (xb < xbe); + while (xa < xae) { + y = (*xa & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*xa++ >> 16) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(xc, z, y); + } +#else + do { + y = *xa++ - *xb++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } while (xb < xbe); + while (xa < xae) { + y = *xa++ - borrow; + borrow = (y & 0x10000) >> 16; + *xc++ = y & 0xffff; + } +#endif +#endif + while (!*--xc) + wa--; + c->wds = wa; + return c; +} + +static double +ulp(double x_) +{ + register Long L; + double_u x, a; + dval(x) = x_; + + L = (word0(x) & Exp_mask) - (P-1)*Exp_msk1; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + if (L > 0) { +#endif +#endif +#ifdef IBM + L |= Exp_msk1 >> 4; +#endif + word0(a) = L; + word1(a) = 0; +#ifndef Avoid_Underflow +#ifndef Sudden_Underflow + } + else { + L = -L >> Exp_shift; + if (L < Exp_shift) { + word0(a) = 0x80000 >> L; + word1(a) = 0; + } + else { + word0(a) = 0; + L -= Exp_shift; + word1(a) = L >= 31 ? 1 : 1 << 31 - L; + } + } +#endif +#endif + return dval(a); +} + +static double +b2d(Bigint *a, int *e) +{ + ULong *xa, *xa0, w, y, z; + int k; + double_u d; +#ifdef VAX + ULong d0, d1; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + + xa0 = a->x; + xa = xa0 + a->wds; + y = *--xa; +#ifdef DEBUG + if (!y) Bug("zero y in b2d"); +#endif + k = hi0bits(y); + *e = 32 - k; +#ifdef Pack_32 + if (k < Ebits) { + d0 = Exp_1 | y >> (Ebits - k); + w = xa > xa0 ? *--xa : 0; + d1 = y << ((32-Ebits) + k) | w >> (Ebits - k); + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + if (k -= Ebits) { + d0 = Exp_1 | y << k | z >> (32 - k); + y = xa > xa0 ? *--xa : 0; + d1 = z << k | y >> (32 - k); + } + else { + d0 = Exp_1 | y; + d1 = z; + } +#else + if (k < Ebits + 16) { + z = xa > xa0 ? *--xa : 0; + d0 = Exp_1 | y << k - Ebits | z >> Ebits + 16 - k; + w = xa > xa0 ? *--xa : 0; + y = xa > xa0 ? *--xa : 0; + d1 = z << k + 16 - Ebits | w << k - Ebits | y >> 16 + Ebits - k; + goto ret_d; + } + z = xa > xa0 ? *--xa : 0; + w = xa > xa0 ? *--xa : 0; + k -= Ebits + 16; + d0 = Exp_1 | y << k + 16 | z << k | w >> 16 - k; + y = xa > xa0 ? *--xa : 0; + d1 = w << k + 16 | y << k; +#endif +ret_d: +#ifdef VAX + word0(d) = d0 >> 16 | d0 << 16; + word1(d) = d1 >> 16 | d1 << 16; +#else +#undef d0 +#undef d1 +#endif + return dval(d); +} + +static Bigint * +d2b(double d_, int *e, int *bits) +{ + double_u d; + Bigint *b; + int de, k; + ULong *x, y, z; +#ifndef Sudden_Underflow + int i; +#endif +#ifdef VAX + ULong d0, d1; +#endif + dval(d) = d_; +#ifdef VAX + d0 = word0(d) >> 16 | word0(d) << 16; + d1 = word1(d) >> 16 | word1(d) << 16; +#else +#define d0 word0(d) +#define d1 word1(d) +#endif + +#ifdef Pack_32 + b = Balloc(1); +#else + b = Balloc(2); +#endif + x = b->x; + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ +#ifdef Sudden_Underflow + de = (int)(d0 >> Exp_shift); +#ifndef IBM + z |= Exp_msk11; +#endif +#else + if ((de = (int)(d0 >> Exp_shift)) != 0) + z |= Exp_msk1; +#endif +#ifdef Pack_32 + if ((y = d1) != 0) { + if ((k = lo0bits(&y)) != 0) { + x[0] = y | z << (32 - k); + z >>= k; + } + else + x[0] = y; +#ifndef Sudden_Underflow + i = +#endif + b->wds = (x[1] = z) ? 2 : 1; + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + x[0] = z; +#ifndef Sudden_Underflow + i = +#endif + b->wds = 1; + k += 32; + } +#else + if (y = d1) { + if (k = lo0bits(&y)) + if (k >= 16) { + x[0] = y | z << 32 - k & 0xffff; + x[1] = z >> k - 16 & 0xffff; + x[2] = z >> k; + i = 2; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16 | z << 16 - k & 0xffff; + x[2] = z >> k & 0xffff; + x[3] = z >> k+16; + i = 3; + } + else { + x[0] = y & 0xffff; + x[1] = y >> 16; + x[2] = z & 0xffff; + x[3] = z >> 16; + i = 3; + } + } + else { +#ifdef DEBUG + if (!z) + Bug("Zero passed to d2b"); +#endif + k = lo0bits(&z); + if (k >= 16) { + x[0] = z; + i = 0; + } + else { + x[0] = z & 0xffff; + x[1] = z >> 16; + i = 1; + } + k += 32; + } + while (!x[i]) + --i; + b->wds = i + 1; +#endif +#ifndef Sudden_Underflow + if (de) { +#endif +#ifdef IBM + *e = (de - Bias - (P-1) << 2) + k; + *bits = 4*P + 8 - k - hi0bits(word0(d) & Frac_mask); +#else + *e = de - Bias - (P-1) + k; + *bits = P - k; +#endif +#ifndef Sudden_Underflow + } + else { + *e = de - Bias - (P-1) + 1 + k; +#ifdef Pack_32 + *bits = 32*i - hi0bits(x[i-1]); +#else + *bits = (i+2)*16 - hi0bits(x[i]); +#endif + } +#endif + return b; +} +#undef d0 +#undef d1 + +static double +ratio(Bigint *a, Bigint *b) +{ + double_u da, db; + int k, ka, kb; + + dval(da) = b2d(a, &ka); + dval(db) = b2d(b, &kb); +#ifdef Pack_32 + k = ka - kb + 32*(a->wds - b->wds); +#else + k = ka - kb + 16*(a->wds - b->wds); +#endif +#ifdef IBM + if (k > 0) { + word0(da) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(da) *= 1 << k; + } + else { + k = -k; + word0(db) += (k >> 2)*Exp_msk1; + if (k &= 3) + dval(db) *= 1 << k; + } +#else + if (k > 0) + word0(da) += k*Exp_msk1; + else { + k = -k; + word0(db) += k*Exp_msk1; + } +#endif + return dval(da) / dval(db); +} + +static const double +tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 +#ifdef VAX + , 1e23, 1e24 +#endif +}; + +static const double +#ifdef IEEE_Arith +bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64, 1e-128, +#ifdef Avoid_Underflow + 9007199254740992.*9007199254740992.e-256 + /* = 2^106 * 1e-53 */ +#else + 1e-256 +#endif +}; +/* The factor of 2^53 in tinytens[4] helps us avoid setting the underflow */ +/* flag unnecessarily. It leads to a song and dance at the end of strtod. */ +#define Scale_Bit 0x10 +#define n_bigtens 5 +#else +#ifdef IBM +bigtens[] = { 1e16, 1e32, 1e64 }; +static const double tinytens[] = { 1e-16, 1e-32, 1e-64 }; +#define n_bigtens 3 +#else +bigtens[] = { 1e16, 1e32 }; +static const double tinytens[] = { 1e-16, 1e-32 }; +#define n_bigtens 2 +#endif +#endif + +#ifndef IEEE_Arith +#undef INFNAN_CHECK +#endif + +#ifdef INFNAN_CHECK + +#ifndef NAN_WORD0 +#define NAN_WORD0 0x7ff80000 +#endif + +#ifndef NAN_WORD1 +#define NAN_WORD1 0 +#endif + +static int +match(const char **sp, char *t) +{ + int c, d; + const char *s = *sp; + + while (d = *t++) { + if ((c = *++s) >= 'A' && c <= 'Z') + c += 'a' - 'A'; + if (c != d) + return 0; + } + *sp = s + 1; + return 1; +} + +#ifndef No_Hex_NaN +static void +hexnan(double *rvp, const char **sp) +{ + ULong c, x[2]; + const char *s; + int havedig, udx0, xshift; + + x[0] = x[1] = 0; + havedig = xshift = 0; + udx0 = 1; + s = *sp; + while (c = *(const unsigned char*)++s) { + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c += 10 - 'a'; + else if (c >= 'A' && c <= 'F') + c += 10 - 'A'; + else if (c <= ' ') { + if (udx0 && havedig) { + udx0 = 0; + xshift = 1; + } + continue; + } + else if (/*(*/ c == ')' && havedig) { + *sp = s + 1; + break; + } + else + return; /* invalid form: don't change *sp */ + havedig = 1; + if (xshift) { + xshift = 0; + x[0] = x[1]; + x[1] = 0; + } + if (udx0) + x[0] = (x[0] << 4) | (x[1] >> 28); + x[1] = (x[1] << 4) | c; + } + if ((x[0] &= 0xfffff) || x[1]) { + word0(*rvp) = Exp_mask | x[0]; + word1(*rvp) = x[1]; + } +} +#endif /*No_Hex_NaN*/ +#endif /* INFNAN_CHECK */ + +NO_SANITIZE("unsigned-integer-overflow", double strtod(const char *s00, char **se)); +double +strtod(const char *s00, char **se) +{ +#ifdef Avoid_Underflow + int scale; +#endif + int bb2, bb5, bbe, bd2, bd5, bbbits, bs2, c, dsign, + e, e1, esign, i, j, k, nd, nd0, nf, nz, nz0, sign; + const char *s, *s0, *s1; + double aadj, adj; + double_u aadj1, rv, rv0; + Long L; + ULong y, z; + Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef USE_LOCALE + const char *s2; +#endif + + errno = 0; + sign = nz0 = nz = 0; + dval(rv) = 0.; + for (s = s00;;s++) + switch (*s) { + case '-': + sign = 1; + /* no break */ + case '+': + if (*++s) + goto break2; + /* no break */ + case 0: + goto ret0; + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case ' ': + continue; + default: + goto break2; + } +break2: + if (*s == '0') { + if (s[1] == 'x' || s[1] == 'X') { + s0 = ++s; + adj = 0; + aadj = 1.0; + nd0 = -4; + + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (*s == '0') { + while (*++s == '0'); + s1 = strchr(hexdigit, *s); + } + if (s1 != NULL) { + do { + adj += aadj * ((s1 - hexdigit) & 15); + nd0 += 4; + aadj /= 16; + } while (*++s && (s1 = strchr(hexdigit, *s))); + } + + if (*s == '.') { + dsign = 1; + if (!*++s || !(s1 = strchr(hexdigit, *s))) goto ret0; + if (nd0 < 0) { + while (*s == '0') { + s++; + nd0 -= 4; + } + } + for (; *s && (s1 = strchr(hexdigit, *s)); ++s) { + adj += aadj * ((s1 - hexdigit) & 15); + if ((aadj /= 16) == 0.0) { + while (strchr(hexdigit, *++s)); + break; + } + } + } + else { + dsign = 0; + } + + if (*s == 'P' || *s == 'p') { + dsign = 0x2C - *++s; /* +: 2B, -: 2D */ + if (abs(dsign) == 1) s++; + else dsign = 1; + + nd = 0; + c = *s; + if (c < '0' || '9' < c) goto ret0; + do { + nd *= 10; + nd += c; + nd -= '0'; + c = *++s; + /* Float("0x0."+("0"*267)+"1fp2095") */ + if (nd + dsign * nd0 > 2095) { + while ('0' <= c && c <= '9') c = *++s; + break; + } + } while ('0' <= c && c <= '9'); + nd0 += nd * dsign; + } + else { + if (dsign) goto ret0; + } + dval(rv) = ldexp(adj, nd0); + goto ret; + } + nz0 = 1; + while (*++s == '0') ; + if (!*s) + goto ret; + } + s0 = s; + y = z = 0; + for (nd = nf = 0; (c = *s) >= '0' && c <= '9'; nd++, s++) + if (nd < 9) + y = 10*y + c - '0'; + else if (nd < DBL_DIG + 2) + z = 10*z + c - '0'; + nd0 = nd; +#ifdef USE_LOCALE + s1 = localeconv()->decimal_point; + if (c == *s1) { + c = '.'; + if (*++s1) { + s2 = s; + for (;;) { + if (*++s2 != *s1) { + c = 0; + break; + } + if (!*++s1) { + s = s2; + break; + } + } + } + } +#endif + if (c == '.') { + if (!ISDIGIT(s[1])) + goto dig_done; + c = *++s; + if (!nd) { + for (; c == '0'; c = *++s) + nz++; + if (c > '0' && c <= '9') { + s0 = s; + nf += nz; + nz = 0; + goto have_dig; + } + goto dig_done; + } + for (; c >= '0' && c <= '9'; c = *++s) { +have_dig: + nz++; + if (nd > DBL_DIG * 4) { + continue; + } + if (c -= '0') { + nf += nz; + for (i = 1; i < nz; i++) + if (nd++ < 9) + y *= 10; + else if (nd <= DBL_DIG + 2) + z *= 10; + if (nd++ < 9) + y = 10*y + c; + else if (nd <= DBL_DIG + 2) + z = 10*z + c; + nz = 0; + } + } + } +dig_done: + e = 0; + if (c == 'e' || c == 'E') { + if (!nd && !nz && !nz0) { + goto ret0; + } + s00 = s; + esign = 0; + switch (c = *++s) { + case '-': + esign = 1; + case '+': + c = *++s; + } + if (c >= '0' && c <= '9') { + while (c == '0') + c = *++s; + if (c > '0' && c <= '9') { + L = c - '0'; + s1 = s; + while ((c = *++s) >= '0' && c <= '9') + L = 10*L + c - '0'; + if (s - s1 > 8 || L > 19999) + /* Avoid confusion from exponents + * so large that e might overflow. + */ + e = 19999; /* safe for 16 bit ints */ + else + e = (int)L; + if (esign) + e = -e; + } + else + e = 0; + } + else + s = s00; + } + if (!nd) { + if (!nz && !nz0) { +#ifdef INFNAN_CHECK + /* Check for Nan and Infinity */ + switch (c) { + case 'i': + case 'I': + if (match(&s,"nf")) { + --s; + if (!match(&s,"inity")) + ++s; + word0(rv) = 0x7ff00000; + word1(rv) = 0; + goto ret; + } + break; + case 'n': + case 'N': + if (match(&s, "an")) { + word0(rv) = NAN_WORD0; + word1(rv) = NAN_WORD1; +#ifndef No_Hex_NaN + if (*s == '(') /*)*/ + hexnan(&rv, &s); +#endif + goto ret; + } + } +#endif /* INFNAN_CHECK */ +ret0: + s = s00; + sign = 0; + } + goto ret; + } + e1 = e -= nf; + + /* Now we have nd0 digits, starting at s0, followed by a + * decimal point, followed by nd-nd0 digits. The number we're + * after is the integer represented by those digits times + * 10**e */ + + if (!nd0) + nd0 = nd; + k = nd < DBL_DIG + 2 ? nd : DBL_DIG + 2; + dval(rv) = y; + if (k > 9) { +#ifdef SET_INEXACT + if (k > DBL_DIG) + oldinexact = get_inexact(); +#endif + dval(rv) = tens[k - 9] * dval(rv) + z; + } + bd0 = bb = bd = bs = delta = 0; + if (nd <= DBL_DIG +#ifndef RND_PRODQUOT +#ifndef Honor_FLT_ROUNDS + && Flt_Rounds == 1 +#endif +#endif + ) { + if (!e) + goto ret; + if (e > 0) { + if (e <= Ten_pmax) { +#ifdef VAX + goto vax_ovfl_check; +#else +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_product(dval(rv), tens[e]); + goto ret; +#endif + } + i = DBL_DIG - nd; + if (e <= Ten_pmax + i) { + /* A fancier test would sometimes let us do + * this for larger i values. + */ +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + e -= i; + dval(rv) *= tens[i]; +#ifdef VAX + /* VAX exponent range is so narrow we must + * worry about overflow here... + */ +vax_ovfl_check: + word0(rv) -= P*Exp_msk1; + /* rv = */ rounded_product(dval(rv), tens[e]); + if ((word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) + goto ovfl; + word0(rv) += P*Exp_msk1; +#else + /* rv = */ rounded_product(dval(rv), tens[e]); +#endif + goto ret; + } + } +#ifndef Inaccurate_Divide + else if (e >= -Ten_pmax) { +#ifdef Honor_FLT_ROUNDS + /* round correctly FLT_ROUNDS = 2 or 3 */ + if (sign) { + dval(rv) = -dval(rv); + sign = 0; + } +#endif + /* rv = */ rounded_quotient(dval(rv), tens[-e]); + goto ret; + } +#endif + } + e1 += nd - k; + +#ifdef IEEE_Arith +#ifdef SET_INEXACT + inexact = 1; + if (k <= DBL_DIG) + oldinexact = get_inexact(); +#endif +#ifdef Avoid_Underflow + scale = 0; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif +#endif /*IEEE_Arith*/ + + /* Get starting approximation = rv * 10**e1 */ + + if (e1 > 0) { + if ((i = e1 & 15) != 0) + dval(rv) *= tens[i]; + if (e1 &= ~15) { + if (e1 > DBL_MAX_10_EXP) { +ovfl: +#ifndef NO_ERRNO + errno = ERANGE; +#endif + /* Can't trust HUGE_VAL */ +#ifdef IEEE_Arith +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: /* toward 0 */ + case 3: /* toward -infinity */ + word0(rv) = Big0; + word1(rv) = Big1; + break; + default: + word0(rv) = Exp_mask; + word1(rv) = 0; + } +#else /*Honor_FLT_ROUNDS*/ + word0(rv) = Exp_mask; + word1(rv) = 0; +#endif /*Honor_FLT_ROUNDS*/ +#ifdef SET_INEXACT + /* set overflow bit */ + dval(rv0) = 1e300; + dval(rv0) *= dval(rv0); +#endif +#else /*IEEE_Arith*/ + word0(rv) = Big0; + word1(rv) = Big1; +#endif /*IEEE_Arith*/ + if (bd0) + goto retfree; + goto ret; + } + e1 >>= 4; + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= bigtens[j]; + /* The last multiplication could overflow. */ + word0(rv) -= P*Exp_msk1; + dval(rv) *= bigtens[j]; + if ((z = word0(rv) & Exp_mask) + > Exp_msk1*(DBL_MAX_EXP+Bias-P)) + goto ovfl; + if (z > Exp_msk1*(DBL_MAX_EXP+Bias-1-P)) { + /* set to largest number */ + /* (Can't trust DBL_MAX) */ + word0(rv) = Big0; + word1(rv) = Big1; + } + else + word0(rv) += P*Exp_msk1; + } + } + else if (e1 < 0) { + e1 = -e1; + if ((i = e1 & 15) != 0) + dval(rv) /= tens[i]; + if (e1 >>= 4) { + if (e1 >= 1 << n_bigtens) + goto undfl; +#ifdef Avoid_Underflow + if (e1 & Scale_Bit) + scale = 2*P; + for (j = 0; e1 > 0; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + if (scale && (j = 2*P + 1 - ((word0(rv) & Exp_mask) + >> Exp_shift)) > 0) { + /* scaled rv is denormal; zap j low bits */ + if (j >= 32) { + word1(rv) = 0; + if (j >= 53) + word0(rv) = (P+2)*Exp_msk1; + else + word0(rv) &= 0xffffffff << (j-32); + } + else + word1(rv) &= 0xffffffff << j; + } +#else + for (j = 0; e1 > 1; j++, e1 >>= 1) + if (e1 & 1) + dval(rv) *= tinytens[j]; + /* The last multiplication could underflow. */ + dval(rv0) = dval(rv); + dval(rv) *= tinytens[j]; + if (!dval(rv)) { + dval(rv) = 2.*dval(rv0); + dval(rv) *= tinytens[j]; +#endif + if (!dval(rv)) { +undfl: + dval(rv) = 0.; +#ifndef NO_ERRNO + errno = ERANGE; +#endif + if (bd0) + goto retfree; + goto ret; + } +#ifndef Avoid_Underflow + word0(rv) = Tiny0; + word1(rv) = Tiny1; + /* The refinement below will clean + * this approximation up. + */ + } +#endif + } + } + + /* Now the hard part -- adjusting rv to the correct value.*/ + + /* Put digits into bd: true value = bd * 10^e */ + + bd0 = s2b(s0, nd0, nd, y); + + for (;;) { + bd = Balloc(bd0->k); + Bcopy(bd, bd0); + bb = d2b(dval(rv), &bbe, &bbbits); /* rv = bb * 2^bbe */ + bs = i2b(1); + + if (e >= 0) { + bb2 = bb5 = 0; + bd2 = bd5 = e; + } + else { + bb2 = bb5 = -e; + bd2 = bd5 = 0; + } + if (bbe >= 0) + bb2 += bbe; + else + bd2 -= bbe; + bs2 = bb2; +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) + bs2++; +#endif +#ifdef Avoid_Underflow + j = bbe - scale; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#else /*Avoid_Underflow*/ +#ifdef Sudden_Underflow +#ifdef IBM + j = 1 + 4*P - 3 - bbbits + ((bbe + bbbits - 1) & 3); +#else + j = P + 1 - bbbits; +#endif +#else /*Sudden_Underflow*/ + j = bbe; + i = j + bbbits - 1; /* logb(rv) */ + if (i < Emin) /* denormal */ + j += P - Emin; + else + j = P + 1 - bbbits; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + bb2 += j; + bd2 += j; +#ifdef Avoid_Underflow + bd2 += scale; +#endif + i = bb2 < bd2 ? bb2 : bd2; + if (i > bs2) + i = bs2; + if (i > 0) { + bb2 -= i; + bd2 -= i; + bs2 -= i; + } + if (bb5 > 0) { + bs = pow5mult(bs, bb5); + bb1 = mult(bs, bb); + Bfree(bb); + bb = bb1; + } + if (bb2 > 0) + bb = lshift(bb, bb2); + if (bd5 > 0) + bd = pow5mult(bd, bd5); + if (bd2 > 0) + bd = lshift(bd, bd2); + if (bs2 > 0) + bs = lshift(bs, bs2); + delta = diff(bb, bd); + dsign = delta->sign; + delta->sign = 0; + i = cmp(delta, bs); +#ifdef Honor_FLT_ROUNDS + if (rounding != 1) { + if (i < 0) { + /* Error is less than an ulp */ + if (!delta->x[0] && delta->wds <= 1) { + /* exact */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (rounding) { + if (dsign) { + adj = 1.; + goto apply_adj; + } + } + else if (!dsign) { + adj = -1.; + if (!word1(rv) + && !(word0(rv) & Frac_mask)) { + y = word0(rv) & Exp_mask; +#ifdef Avoid_Underflow + if (!scale || y > 2*P*Exp_msk1) +#else + if (y) +#endif + { + delta = lshift(delta,Log2P); + if (cmp(delta, bs) <= 0) + adj = -0.5; + } + } +apply_adj: +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) + <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= + P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + dval(rv) += adj*ulp(dval(rv)); + word0(rv) -= P*Exp_msk1; + } + else +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + dval(rv) += adj*ulp(dval(rv)); + } + break; + } + adj = ratio(delta, bs); + if (adj < 1.) + adj = 1.; + if (adj <= 0x7ffffffe) { + /* adj = rounding ? ceil(adj) : floor(adj); */ + y = adj; + if (y != adj) { + if (!((rounding>>1) ^ dsign)) + y++; + adj = y; + } + } +#ifdef Avoid_Underflow + if (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + word0(adj) += (2*P+1)*Exp_msk1 - y; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + word0(rv) += P*Exp_msk1; + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + word0(rv) -= P*Exp_msk1; + goto cont; + } +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + adj *= ulp(dval(rv)); + if (dsign) + dval(rv) += adj; + else + dval(rv) -= adj; + goto cont; + } +#endif /*Honor_FLT_ROUNDS*/ + + if (i < 0) { + /* Error is less than half an ulp -- check for + * special case of mantissa a power of two. + */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask +#ifdef IEEE_Arith +#ifdef Avoid_Underflow + || (word0(rv) & Exp_mask) <= (2*P+1)*Exp_msk1 +#else + || (word0(rv) & Exp_mask) <= Exp_msk1 +#endif +#endif + ) { +#ifdef SET_INEXACT + if (!delta->x[0] && delta->wds <= 1) + inexact = 0; +#endif + break; + } + if (!delta->x[0] && delta->wds <= 1) { + /* exact result */ +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + delta = lshift(delta,Log2P); + if (cmp(delta, bs) > 0) + goto drop_down; + break; + } + if (i == 0) { + /* exactly half-way between */ + if (dsign) { + if ((word0(rv) & Bndry_mask1) == Bndry_mask1 + && word1(rv) == ( +#ifdef Avoid_Underflow + (scale && (y = word0(rv) & Exp_mask) <= 2*P*Exp_msk1) + ? (0xffffffff & (0xffffffff << (2*P+1-(y>>Exp_shift)))) : +#endif + 0xffffffff)) { + /*boundary case -- increment exponent*/ + word0(rv) = (word0(rv) & Exp_mask) + + Exp_msk1 +#ifdef IBM + | Exp_msk1 >> 4 +#endif + ; + word1(rv) = 0; +#ifdef Avoid_Underflow + dsign = 0; +#endif + break; + } + } + else if (!(word0(rv) & Bndry_mask) && !word1(rv)) { +drop_down: + /* boundary case -- decrement exponent */ +#ifdef Sudden_Underflow /*{{*/ + L = word0(rv) & Exp_mask; +#ifdef IBM + if (L < Exp_msk1) +#else +#ifdef Avoid_Underflow + if (L <= (scale ? (2*P+1)*Exp_msk1 : Exp_msk1)) +#else + if (L <= Exp_msk1) +#endif /*Avoid_Underflow*/ +#endif /*IBM*/ + goto undfl; + L -= Exp_msk1; +#else /*Sudden_Underflow}{*/ +#ifdef Avoid_Underflow + if (scale) { + L = word0(rv) & Exp_mask; + if (L <= (2*P+1)*Exp_msk1) { + if (L > (P+2)*Exp_msk1) + /* round even ==> */ + /* accept rv */ + break; + /* rv = smallest denormal */ + goto undfl; + } + } +#endif /*Avoid_Underflow*/ + L = (word0(rv) & Exp_mask) - Exp_msk1; +#endif /*Sudden_Underflow}}*/ + word0(rv) = L | Bndry_mask1; + word1(rv) = 0xffffffff; +#ifdef IBM + goto cont; +#else + break; +#endif + } +#ifndef ROUND_BIASED + if (!(word1(rv) & LSB)) + break; +#endif + if (dsign) + dval(rv) += ulp(dval(rv)); +#ifndef ROUND_BIASED + else { + dval(rv) -= ulp(dval(rv)); +#ifndef Sudden_Underflow + if (!dval(rv)) + goto undfl; +#endif + } +#ifdef Avoid_Underflow + dsign = 1 - dsign; +#endif +#endif + break; + } + if ((aadj = ratio(delta, bs)) <= 2.) { + if (dsign) + aadj = dval(aadj1) = 1.; + else if (word1(rv) || word0(rv) & Bndry_mask) { +#ifndef Sudden_Underflow + if (word1(rv) == Tiny1 && !word0(rv)) + goto undfl; +#endif + aadj = 1.; + dval(aadj1) = -1.; + } + else { + /* special case -- power of FLT_RADIX to be */ + /* rounded down... */ + + if (aadj < 2./FLT_RADIX) + aadj = 1./FLT_RADIX; + else + aadj *= 0.5; + dval(aadj1) = -aadj; + } + } + else { + aadj *= 0.5; + dval(aadj1) = dsign ? aadj : -aadj; +#ifdef Check_FLT_ROUNDS + switch (Rounding) { + case 2: /* towards +infinity */ + dval(aadj1) -= 0.5; + break; + case 0: /* towards 0 */ + case 3: /* towards -infinity */ + dval(aadj1) += 0.5; + } +#else + if (Flt_Rounds == 0) + dval(aadj1) += 0.5; +#endif /*Check_FLT_ROUNDS*/ + } + y = word0(rv) & Exp_mask; + + /* Check for overflow */ + + if (y == Exp_msk1*(DBL_MAX_EXP+Bias-1)) { + dval(rv0) = dval(rv); + word0(rv) -= P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + if ((word0(rv) & Exp_mask) >= + Exp_msk1*(DBL_MAX_EXP+Bias-P)) { + if (word0(rv0) == Big0 && word1(rv0) == Big1) + goto ovfl; + word0(rv) = Big0; + word1(rv) = Big1; + goto cont; + } + else + word0(rv) += P*Exp_msk1; + } + else { +#ifdef Avoid_Underflow + if (scale && y <= 2*P*Exp_msk1) { + if (aadj <= 0x7fffffff) { + if ((z = (int)aadj) <= 0) + z = 1; + aadj = z; + dval(aadj1) = dsign ? aadj : -aadj; + } + word0(aadj1) += (2*P+1)*Exp_msk1 - y; + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#else +#ifdef Sudden_Underflow + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) { + dval(rv0) = dval(rv); + word0(rv) += P*Exp_msk1; + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#ifdef IBM + if ((word0(rv) & Exp_mask) < P*Exp_msk1) +#else + if ((word0(rv) & Exp_mask) <= P*Exp_msk1) +#endif + { + if (word0(rv0) == Tiny0 && word1(rv0) == Tiny1) + goto undfl; + word0(rv) = Tiny0; + word1(rv) = Tiny1; + goto cont; + } + else + word0(rv) -= P*Exp_msk1; + } + else { + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; + } +#else /*Sudden_Underflow*/ + /* Compute adj so that the IEEE rounding rules will + * correctly round rv + adj in some half-way cases. + * If rv * ulp(rv) is denormalized (i.e., + * y <= (P-1)*Exp_msk1), we must adjust aadj to avoid + * trouble from bits lost to denormalization; + * example: 1.2e-307 . + */ + if (y <= (P-1)*Exp_msk1 && aadj > 1.) { + dval(aadj1) = (double)(int)(aadj + 0.5); + if (!dsign) + dval(aadj1) = -dval(aadj1); + } + adj = dval(aadj1) * ulp(dval(rv)); + dval(rv) += adj; +#endif /*Sudden_Underflow*/ +#endif /*Avoid_Underflow*/ + } + z = word0(rv) & Exp_mask; +#ifndef SET_INEXACT +#ifdef Avoid_Underflow + if (!scale) +#endif + if (y == z) { + /* Can we stop now? */ + L = (Long)aadj; + aadj -= L; + /* The tolerances below are conservative. */ + if (dsign || word1(rv) || word0(rv) & Bndry_mask) { + if (aadj < .4999999 || aadj > .5000001) + break; + } + else if (aadj < .4999999/FLT_RADIX) + break; + } +#endif +cont: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(delta); + } +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(rv0) = Exp_1 + (70 << Exp_shift); + word1(rv0) = 0; + dval(rv0) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif +#ifdef Avoid_Underflow + if (scale) { + word0(rv0) = Exp_1 - 2*P*Exp_msk1; + word1(rv0) = 0; + dval(rv) *= dval(rv0); +#ifndef NO_ERRNO + /* try to avoid the bug of testing an 8087 register value */ + if (word0(rv) == 0 && word1(rv) == 0) + errno = ERANGE; +#endif + } +#endif /* Avoid_Underflow */ +#ifdef SET_INEXACT + if (inexact && !(word0(rv) & Exp_mask)) { + /* set underflow bit */ + dval(rv0) = 1e-300; + dval(rv0) *= dval(rv0); + } +#endif +retfree: + Bfree(bb); + Bfree(bd); + Bfree(bs); + Bfree(bd0); + Bfree(delta); +ret: + if (se) + *se = (char *)s; + return sign ? -dval(rv) : dval(rv); +} + +NO_SANITIZE("unsigned-integer-overflow", static int quorem(Bigint *b, Bigint *S)); +static int +quorem(Bigint *b, Bigint *S) +{ + int n; + ULong *bx, *bxe, q, *sx, *sxe; +#ifdef ULLong + ULLong borrow, carry, y, ys; +#else + ULong borrow, carry, y, ys; +#ifdef Pack_32 + ULong si, z, zs; +#endif +#endif + + n = S->wds; +#ifdef DEBUG + /*debug*/ if (b->wds > n) + /*debug*/ Bug("oversize b in quorem"); +#endif + if (b->wds < n) + return 0; + sx = S->x; + sxe = sx + --n; + bx = b->x; + bxe = bx + n; + q = *bxe / (*sxe + 1); /* ensure q <= true quotient */ +#ifdef DEBUG + /*debug*/ if (q > 9) + /*debug*/ Bug("oversized quotient in quorem"); +#endif + if (q) { + borrow = 0; + carry = 0; + do { +#ifdef ULLong + ys = *sx++ * (ULLong)q + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) * q + carry; + zs = (si >> 16) * q + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ * q + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + if (!*bxe) { + bx = b->x; + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + if (cmp(b, S) >= 0) { + q++; + borrow = 0; + carry = 0; + bx = b->x; + sx = S->x; + do { +#ifdef ULLong + ys = *sx++ + carry; + carry = ys >> 32; + y = *bx - (ys & FFFFFFFF) - borrow; + borrow = y >> 32 & (ULong)1; + *bx++ = (ULong)(y & FFFFFFFF); +#else +#ifdef Pack_32 + si = *sx++; + ys = (si & 0xffff) + carry; + zs = (si >> 16) + (ys >> 16); + carry = zs >> 16; + y = (*bx & 0xffff) - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + z = (*bx >> 16) - (zs & 0xffff) - borrow; + borrow = (z & 0x10000) >> 16; + Storeinc(bx, z, y); +#else + ys = *sx++ + carry; + carry = ys >> 16; + y = *bx - (ys & 0xffff) - borrow; + borrow = (y & 0x10000) >> 16; + *bx++ = y & 0xffff; +#endif +#endif + } while (sx <= sxe); + bx = b->x; + bxe = bx + n; + if (!*bxe) { + while (--bxe > bx && !*bxe) + --n; + b->wds = n; + } + } + return q; +} + +#ifndef MULTIPLE_THREADS +static char *dtoa_result; +#endif + +#ifndef MULTIPLE_THREADS +static char * +rv_alloc(int i) +{ + return dtoa_result = MALLOC(i); +} +#else +#define rv_alloc(i) MALLOC(i) +#endif + +static char * +nrv_alloc(const char *s, char **rve, size_t n) +{ + char *rv, *t; + + t = rv = rv_alloc(n); + while ((*t = *s++) != 0) t++; + if (rve) + *rve = t; + return rv; +} + +#define rv_strdup(s, rve) nrv_alloc((s), (rve), strlen(s)+1) + +#ifndef MULTIPLE_THREADS +/* freedtoa(s) must be used to free values s returned by dtoa + * when MULTIPLE_THREADS is #defined. It should be used in all cases, + * but for consistency with earlier versions of dtoa, it is optional + * when MULTIPLE_THREADS is not defined. + */ + +static void +freedtoa(char *s) +{ + FREE(s); +} +#endif + +static const char INFSTR[] = "Infinity"; +static const char NANSTR[] = "NaN"; +static const char ZEROSTR[] = "0"; + +/* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 112-126]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + +char * +dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve) +{ + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int bbits, b2, b5, be, dig, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, k_check, leftright, m2, m5, s2, s5, + spec_case, try_quick, half = 0; + Long L; +#ifndef Sudden_Underflow + int denorm; + ULong x; +#endif + Bigint *b, *b1, *delta, *mlo = 0, *mhi = 0, *S; + double ds; + double_u d, d2, eps; + char *s, *s0; +#ifdef Honor_FLT_ROUNDS + int rounding; +#endif +#ifdef SET_INEXACT + int inexact, oldinexact; +#endif + + dval(d) = d_; + +#ifndef MULTIPLE_THREADS + if (dtoa_result) { + freedtoa(dtoa_result); + dtoa_result = 0; + } +#endif + + if (word0(d) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(d) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + +#if defined(IEEE_Arith) + defined(VAX) +#ifdef IEEE_Arith + if ((word0(d) & Exp_mask) == Exp_mask) +#else + if (word0(d) == 0x8000) +#endif + { + /* Infinity or NaN */ + *decpt = 9999; +#ifdef IEEE_Arith + if (!word1(d) && !(word0(d) & 0xfffff)) + return rv_strdup(INFSTR, rve); +#endif + return rv_strdup(NANSTR, rve); + } +#endif +#ifdef IBM + dval(d) += 0; /* normalize */ +#endif + if (!dval(d)) { + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + +#ifdef SET_INEXACT + try_quick = oldinexact = get_inexact(); + inexact = 1; +#endif +#ifdef Honor_FLT_ROUNDS + if ((rounding = Flt_Rounds) >= 2) { + if (*sign) + rounding = rounding == 2 ? 0 : 2; + else + if (rounding != 2) + rounding = 0; + } +#endif + + b = d2b(dval(d), &be, &bbits); +#ifdef Sudden_Underflow + i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1)); +#else + if ((i = (int)(word0(d) >> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { +#endif + dval(d2) = dval(d); + word0(d2) &= Frac_mask1; + word0(d2) |= Exp_11; +#ifdef IBM + if (j = 11 - hi0bits(word0(d2) & Frac_mask)) + dval(d2) /= 1 << j; +#endif + + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + + i -= Bias; +#ifdef IBM + i <<= 2; + i += j; +#endif +#ifndef Sudden_Underflow + denorm = 0; + } + else { + /* d is denormalized */ + + i = bbits + be + (Bias + (P-1) - 1); + x = i > 32 ? word0(d) << (64 - i) | word1(d) >> (i - 32) + : word1(d) << (32 - i); + dval(d2) = x; + word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + i -= (Bias + (P-1) - 1) + 1; + denorm = 1; + } +#endif + ds = (dval(d2)-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0. && ds != k) + k--; /* want k = floor(ds) */ + k_check = 1; + if (k >= 0 && k <= Ten_pmax) { + if (dval(d) < tens[k]) + k--; + k_check = 0; + } + j = bbits - i - 1; + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + if (mode < 0 || mode > 9) + mode = 0; + +#ifndef SET_INEXACT +#ifdef Check_FLT_ROUNDS + try_quick = Rounding == 1; +#else + try_quick = 1; +#endif +#endif /*SET_INEXACT*/ + + if (mode > 5) { + mode -= 4; + try_quick = 0; + } + leftright = 1; + ilim = ilim1 = -1; + switch (mode) { + case 0: + case 1: + i = 18; + ndigits = 0; + break; + case 2: + leftright = 0; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = 0; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + s = s0 = rv_alloc(i+1); + +#ifdef Honor_FLT_ROUNDS + if (mode > 1 && rounding != 1) + leftright = 0; +#endif + + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + dval(d2) = dval(d); + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if (j & Bletch) { + /* prevent overflows */ + j &= Bletch - 1; + dval(d) /= bigtens[n_bigtens-1]; + ieps++; + } + for (; j; j >>= 1, i++) + if (j & 1) { + ieps++; + ds *= bigtens[i]; + } + dval(d) /= ds; + } + else if ((j1 = -k) != 0) { + dval(d) *= tens[j1 & 0xf]; + for (j = j1 >> 4; j; j >>= 1, i++) + if (j & 1) { + ieps++; + dval(d) *= bigtens[i]; + } + } + if (k_check && dval(d) < 1. && ilim > 0) { + if (ilim1 <= 0) + goto fast_failed; + ilim = ilim1; + k--; + dval(d) *= 10.; + ieps++; + } + dval(eps) = ieps*dval(d) + 7.; + word0(eps) -= (P-1)*Exp_msk1; + if (ilim == 0) { + S = mhi = 0; + dval(d) -= 5.; + if (dval(d) > dval(eps)) + goto one_digit; + if (dval(d) < -dval(eps)) + goto no_digits; + goto fast_failed; + } +#ifndef No_leftright + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + dval(eps) = 0.5/tens[ilim-1] - dval(eps); + for (i = 0;;) { + L = (int)dval(d); + dval(d) -= L; + *s++ = '0' + (int)L; + if (dval(d) < dval(eps)) + goto ret1; + if (1. - dval(d) < dval(eps)) + goto bump_up; + if (++i >= ilim) + break; + dval(eps) *= 10.; + dval(d) *= 10.; + } + } + else { +#endif + /* Generate ilim digits, then fix them up. */ + dval(eps) *= tens[ilim-1]; + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d)); + if (!(dval(d) -= L)) + ilim = i; + *s++ = '0' + (int)L; + if (i == ilim) { + if (dval(d) > 0.5 + dval(eps)) + goto bump_up; + else if (dval(d) < 0.5 - dval(eps)) { + while (*--s == '0') ; + s++; + goto ret1; + } + half = 1; + if ((*(s-1) - '0') & 1) { + goto bump_up; + } + break; + } + } +#ifndef No_leftright + } +#endif +fast_failed: + s = s0; + dval(d) = dval(d2); + k = k0; + ilim = ilim0; + } + + /* Do we have a "small" integer? */ + + if (be >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = 0; + if (ilim < 0 || dval(d) <= 5*ds) + goto no_digits; + goto one_digit; + } + for (i = 1;; i++, dval(d) *= 10.) { + L = (Long)(dval(d) / ds); + dval(d) -= L*ds; +#ifdef Check_FLT_ROUNDS + /* If FLT_ROUNDS == 2, L will usually be high by 1 */ + if (dval(d) < 0) { + L--; + dval(d) += ds; + } +#endif + *s++ = '0' + (int)L; + if (!dval(d)) { +#ifdef SET_INEXACT + inexact = 0; +#endif + break; + } + if (i == ilim) { +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto ret1; + case 2: goto bump_up; + } +#endif + dval(d) += dval(d); + if (dval(d) > ds || (dval(d) == ds && (L & 1))) { +bump_up: + while (*--s == '9') + if (s == s0) { + k++; + *s = '0'; + break; + } + ++*s++; + } + break; + } + } + goto ret1; + } + + m2 = b2; + m5 = b5; + if (leftright) { + i = +#ifndef Sudden_Underflow + denorm ? be + (Bias + (P-1) - 1 + 1) : +#endif +#ifdef IBM + 1 + 4*P - 3 - bbits + ((bbits + be - 1) & 3); +#else + 1 + P - bbits; +#endif + b2 += i; + s2 += i; + mhi = i2b(1); + } + if (m2 > 0 && s2 > 0) { + i = m2 < s2 ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mult(mhi, b); + Bfree(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + S = i2b(1); + if (s5 > 0) + S = pow5mult(S, s5); + + /* Check for special case that d is a normalized power of 2. */ + + spec_case = 0; + if ((mode < 2 || leftright) +#ifdef Honor_FLT_ROUNDS + && rounding == 1 +#endif + ) { + if (!word1(d) && !(word0(d) & Bndry_mask) +#ifndef Sudden_Underflow + && word0(d) & (Exp_mask & ~Exp_msk1) +#endif + ) { + /* The special case */ + b2 += Log2P; + s2 += Log2P; + spec_case = 1; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ +#ifdef Pack_32 + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0x1f) != 0) + i = 32 - i; +#else + if ((i = ((s5 ? 32 - hi0bits(S->x[S->wds-1]) : 1) + s2) & 0xf) != 0) + i = 16 - i; +#endif + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + if (b2 > 0) + b = lshift(b, b2); + if (s2 > 0) + S = lshift(S, s2); + if (k_check) { + if (cmp(b,S) < 0) { + k--; + b = multadd(b, 10, 0); /* we botched the k estimate */ + if (leftright) + mhi = multadd(mhi, 10, 0); + ilim = ilim1; + } + } + if (ilim <= 0 && (mode == 3 || mode == 5)) { + if (ilim < 0 || cmp(b,S = multadd(S,5,0)) <= 0) { + /* no digits, fcvt style */ +no_digits: + k = -1 - ndigits; + goto ret; + } +one_digit: + *s++ = '1'; + k++; + goto ret; + } + if (leftright) { + if (m2 > 0) + mhi = lshift(mhi, m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = Balloc(mhi->k); + Bcopy(mhi, mlo); + mhi = lshift(mhi, Log2P); + } + + for (i = 1;;i++) { + dig = quorem(b,S) + '0'; + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = cmp(b, mlo); + delta = diff(S, mhi); + j1 = delta->sign ? 1 : cmp(b, delta); + Bfree(delta); +#ifndef ROUND_BIASED + if (j1 == 0 && mode != 1 && !(word1(d) & 1) +#ifdef Honor_FLT_ROUNDS + && rounding >= 1 +#endif + ) { + if (dig == '9') + goto round_9_up; + if (j > 0) + dig++; +#ifdef SET_INEXACT + else if (!b->x[0] && b->wds <= 1) + inexact = 0; +#endif + *s++ = dig; + goto ret; + } +#endif + if (j < 0 || (j == 0 && mode != 1 +#ifndef ROUND_BIASED + && !(word1(d) & 1) +#endif + )) { + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto accept_dig; + } +#ifdef Honor_FLT_ROUNDS + if (mode > 1) + switch (rounding) { + case 0: goto accept_dig; + case 2: goto keep_dig; + } +#endif /*Honor_FLT_ROUNDS*/ + if (j1 > 0) { + b = lshift(b, 1); + j1 = cmp(b, S); + if ((j1 > 0 || (j1 == 0 && (dig & 1))) && dig++ == '9') + goto round_9_up; + } +accept_dig: + *s++ = dig; + goto ret; + } + if (j1 > 0) { +#ifdef Honor_FLT_ROUNDS + if (!rounding) + goto accept_dig; +#endif + if (dig == '9') { /* possible if i == 1 */ +round_9_up: + *s++ = '9'; + goto roundoff; + } + *s++ = dig + 1; + goto ret; + } +#ifdef Honor_FLT_ROUNDS +keep_dig: +#endif + *s++ = dig; + if (i == ilim) + break; + b = multadd(b, 10, 0); + if (mlo == mhi) + mlo = mhi = multadd(mhi, 10, 0); + else { + mlo = multadd(mlo, 10, 0); + mhi = multadd(mhi, 10, 0); + } + } + } + else + for (i = 1;; i++) { + *s++ = dig = quorem(b,S) + '0'; + if (!b->x[0] && b->wds <= 1) { +#ifdef SET_INEXACT + inexact = 0; +#endif + goto ret; + } + if (i >= ilim) + break; + b = multadd(b, 10, 0); + } + + /* Round off last digit */ + +#ifdef Honor_FLT_ROUNDS + switch (rounding) { + case 0: goto trimzeros; + case 2: goto roundoff; + } +#endif + b = lshift(b, 1); + j = cmp(b, S); + if (j > 0 || (j == 0 && (dig & 1))) { + roundoff: + while (*--s == '9') + if (s == s0) { + k++; + *s++ = '1'; + goto ret; + } + if (!half || (*s - '0') & 1) + ++*s; + } + else { + while (*--s == '0') ; + } + s++; +ret: + Bfree(S); + if (mhi) { + if (mlo && mlo != mhi) + Bfree(mlo); + Bfree(mhi); + } +ret1: +#ifdef SET_INEXACT + if (inexact) { + if (!oldinexact) { + word0(d) = Exp_1 + (70 << Exp_shift); + word1(d) = 0; + dval(d) += 1.; + } + } + else if (!oldinexact) + clear_inexact(); +#endif + Bfree(b); + *s = 0; + *decpt = k + 1; + if (rve) + *rve = s; + return s0; +} + +/*- + * Copyright (c) 2004-2008 David Schultz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define DBL_MANH_SIZE 20 +#define DBL_MANL_SIZE 32 +#define DBL_ADJ (DBL_MAX_EXP - 2) +#define SIGFIGS ((DBL_MANT_DIG + 3) / 4 + 1) +#define dexp_get(u) ((int)(word0(u) >> Exp_shift) & ~Exp_msk1) +#define dexp_set(u,v) (word0(u) = (((int)(word0(u)) & ~Exp_mask) | ((v) << Exp_shift))) +#define dmanh_get(u) ((uint32_t)(word0(u) & Frac_mask)) +#define dmanl_get(u) ((uint32_t)word1(u)) + + +/* + * This procedure converts a double-precision number in IEEE format + * into a string of hexadecimal digits and an exponent of 2. Its + * behavior is bug-for-bug compatible with dtoa() in mode 2, with the + * following exceptions: + * + * - An ndigits < 0 causes it to use as many digits as necessary to + * represent the number exactly. + * - The additional xdigs argument should point to either the string + * "0123456789ABCDEF" or the string "0123456789abcdef", depending on + * which case is desired. + * - This routine does not repeat dtoa's mistake of setting decpt + * to 9999 in the case of an infinity or NaN. INT_MAX is used + * for this purpose instead. + * + * Note that the C99 standard does not specify what the leading digit + * should be for non-zero numbers. For instance, 0x1.3p3 is the same + * as 0x2.6p2 is the same as 0x4.cp3. This implementation always makes + * the leading digit a 1. This ensures that the exponent printed is the + * actual base-2 exponent, i.e., ilogb(d). + * + * Inputs: d, xdigs, ndigits + * Outputs: decpt, sign, rve + */ +char * +hdtoa(double d, const char *xdigs, int ndigits, int *decpt, int *sign, char **rve) +{ + U u; + char *s, *s0; + int bufsize; + uint32_t manh, manl; + + u.d = d; + if (word0(u) & Sign_bit) { + /* set sign for everything, including 0's and NaNs */ + *sign = 1; + word0(u) &= ~Sign_bit; /* clear sign bit */ + } + else + *sign = 0; + + if (isinf(d)) { /* FP_INFINITE */ + *decpt = INT_MAX; + return rv_strdup(INFSTR, rve); + } + else if (isnan(d)) { /* FP_NAN */ + *decpt = INT_MAX; + return rv_strdup(NANSTR, rve); + } + else if (d == 0.0) { /* FP_ZERO */ + *decpt = 1; + return rv_strdup(ZEROSTR, rve); + } + else if (dexp_get(u)) { /* FP_NORMAL */ + *decpt = dexp_get(u) - DBL_ADJ; + } + else { /* FP_SUBNORMAL */ + u.d *= 5.363123171977039e+154 /* 0x1p514 */; + *decpt = dexp_get(u) - (514 + DBL_ADJ); + } + + if (ndigits == 0) /* dtoa() compatibility */ + ndigits = 1; + + /* + * If ndigits < 0, we are expected to auto-size, so we allocate + * enough space for all the digits. + */ + bufsize = (ndigits > 0) ? ndigits : SIGFIGS; + s0 = rv_alloc(bufsize+1); + + /* Round to the desired number of digits. */ + if (SIGFIGS > ndigits && ndigits > 0) { + float redux = 1.0f; + int offset = 4 * ndigits + DBL_MAX_EXP - 4 - DBL_MANT_DIG; + dexp_set(u, offset); + u.d += redux; + u.d -= redux; + *decpt += dexp_get(u) - offset; + } + + manh = dmanh_get(u); + manl = dmanl_get(u); + *s0 = '1'; + for (s = s0 + 1; s < s0 + bufsize; s++) { + *s = xdigs[(manh >> (DBL_MANH_SIZE - 4)) & 0xf]; + manh = (manh << 4) | (manl >> (DBL_MANL_SIZE - 4)); + manl <<= 4; + } + + /* If ndigits < 0, we are expected to auto-size the precision. */ + if (ndigits < 0) { + for (ndigits = SIGFIGS; s0[ndigits - 1] == '0'; ndigits--) + ; + } + + s = s0 + ndigits; + *s = '\0'; + if (rve != NULL) + *rve = s; + return (s0); +} + +#ifdef __cplusplus +#if 0 +{ /* satisfy cc-mode */ +#endif +} +#endif From d071a0abbbf6c98453c3e17789e9d51250f6a39c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 01:30:37 +0900 Subject: [PATCH 069/144] Add new files in gemspec --- bigdecimal.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 86ea1801..ef003e0e 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -21,7 +21,9 @@ Gem::Specification.new do |s| ext/bigdecimal/bigdecimal.h ext/bigdecimal/bits.h ext/bigdecimal/feature.h + ext/bigdecimal/missing.c ext/bigdecimal/missing.h + ext/bigdecimal/missing/dtoa.c ext/bigdecimal/static_assert.h lib/bigdecimal.rb lib/bigdecimal/jacobian.rb From 2dbe170e35aa041795d42b66c09b1425226e5996 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 10:28:23 +0900 Subject: [PATCH 070/144] Allow digits=0 in BigDecimal(flt) and Float#to_d Using dtoa of mode=0, we can determine the number of digits in decimal that is necessary to represent the given Float number without errors. This change permits digits=0 in BigDecimal(flt) and Float#to_d, and these methods use dtoa of mode=0 when the given digits is 0. Internal implicit conversion from Float also uses digits=0. [Fix GH-70] --- ext/bigdecimal/bigdecimal.c | 53 +++++++++++++++---------- lib/bigdecimal/util.rb | 2 +- test/bigdecimal/test_bigdecimal.rb | 2 + test/bigdecimal/test_bigdecimal_util.rb | 8 ++-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a2f78063..e352f381 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -947,7 +947,7 @@ BigDecimal_coerce(VALUE self, VALUE other) Real *b; if (RB_TYPE_P(other, T_FLOAT)) { - GUARD_OBJ(b, GetVpValueWithPrec(other, DBLE_FIG, 1)); + GUARD_OBJ(b, GetVpValueWithPrec(other, 0, 1)); obj = rb_assoc_new(VpCheckGetValue(b), self); } else { @@ -1005,7 +1005,7 @@ BigDecimal_add(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1063,7 +1063,7 @@ BigDecimal_sub(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self,1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1113,7 +1113,7 @@ BigDecimalCmp(VALUE self, VALUE r,char op) break; case T_FLOAT: - GUARD_OBJ(b, GetVpValueWithPrec(r, DBLE_FIG, 0)); + GUARD_OBJ(b, GetVpValueWithPrec(r, 0, 0)); break; case T_RATIONAL: @@ -1326,7 +1326,7 @@ BigDecimal_mult(VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1354,7 +1354,7 @@ BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1420,7 +1420,7 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -1521,7 +1521,7 @@ BigDecimal_divremain(VALUE self, VALUE r, Real **dv, Real **rv) GUARD_OBJ(a, GetVpValue(self, 1)); if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, DBLE_FIG, 1); + b = GetVpValueWithPrec(r, 0, 1); } else if (RB_TYPE_P(r, T_RATIONAL)) { b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); @@ -2416,7 +2416,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) if (NIL_P(prec)) { n += DBLE_FIG; } - exp = GetVpValueWithPrec(vexp, DBLE_FIG, 1); + exp = GetVpValueWithPrec(vexp, 0, 1); break; case T_RATIONAL: @@ -2832,7 +2832,8 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) char buf[DBLE_FIG + BASE_FIG + 2 + 1]; int decpt, negative_p; char *e; - char *p = BigDecimal_dtoa(d, 2, digs, &decpt, &negative_p, &e); + const int mode = digs == 0 ? 0 : 2; + char *p = BigDecimal_dtoa(d, mode, digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); if (len10 >= (int)sizeof(buf)) len10 = (int)sizeof(buf) - 1; @@ -3009,6 +3010,7 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) VALUE copy = TypedData_Wrap_Struct(rb_cBigDecimal, &BigDecimal_data_type, 0); vp = VpCopy(NULL, vp); + /* TODO: rounding */ BigDecimal_wrap_struct(copy, vp); return VpCheckGetValue(vp); } @@ -3049,19 +3051,28 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } /* call-seq: - * BigDecimal(initial, digits=0, exception: true) + * BigDecimal(arg, exception: true) + * BigDecimal(arg, digits, exception: true) * - * Create a new BigDecimal object. + * Returns arg converted to a BigDecimal. Numeric types are converted + * directly. Other types except for String are first converted to String + * by to_str. Strings can be converted when it has appropriate + * forms of decimal numbers. Exceptions can be suppressed by passing + * exception: false. * - * initial:: The initial value, as an Integer, a Float, a Rational, - * a BigDecimal, or a String. + * When arg is a Float and digits is 0, the number + * of digits is determined by the algorithm of dtoa function + * written by David M. Gay. That algorithm is based on "How to Print Floating- + * Point Numbers Accurately" by Guy L. Steele, Jr. and Jon L. White [Proc. ACM + * SIGPLAN '90, pp. 112-126]. * - * If it is a String, spaces are ignored and unrecognized characters - * terminate the value. + * arg:: The value converted to a BigDecimal. * - * digits:: The number of significant digits, as an Integer. If omitted or 0, - * the number of significant digits is determined from the initial - * value. + * If it is a String, spaces are ignored and unrecognized characters + * terminate the value. + * + * digits:: The number of significant digits, as an Integer. If omitted, + * the number of significant digits is determined from arg. * * The actual number of significant digits used in computation is * usually larger than the specified number. @@ -3306,7 +3317,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 0); + vx = GetVpValueWithPrec(x, 0, 0); } break; @@ -3459,7 +3470,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) infinite = isinf(flo); nan = isnan(flo); if (!zero && !negative && !infinite && !nan) { - vx = GetVpValueWithPrec(x, DBLE_FIG, 1); + vx = GetVpValueWithPrec(x, 0, 1); } break; diff --git a/lib/bigdecimal/util.rb b/lib/bigdecimal/util.rb index 00a3e967..cb645d2a 100644 --- a/lib/bigdecimal/util.rb +++ b/lib/bigdecimal/util.rb @@ -43,7 +43,7 @@ class Float < Numeric # # See also BigDecimal::new. # - def to_d(precision=Float::DIG+1) + def to_d(precision=0) BigDecimal(self, precision) end end diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3d3a750a..b015f9a9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -915,6 +915,7 @@ def test_mult def test_mult_with_float assert_kind_of(BigDecimal, BigDecimal("3") * 1.5) + assert_equal(BigDecimal("64.4"), BigDecimal(1) * 64.4) end def test_mult_with_rational @@ -953,6 +954,7 @@ def test_div def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) + assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) end def test_div_with_rational diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index 9c6973dc..ffd4c5f6 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -19,11 +19,11 @@ def test_Integer_to_d def test_Float_to_d_without_precision delta = 1.0/10**(Float::DIG+1) - assert_in_delta(BigDecimal(0.5, Float::DIG+1), 0.5.to_d, delta) - assert_in_delta(BigDecimal(355.0/113.0, Float::DIG+1), (355.0/113.0).to_d, delta) + assert_in_delta(BigDecimal(0.5, 0), 0.5.to_d, delta) + assert_in_delta(BigDecimal(355.0/113.0, 0), (355.0/113.0).to_d, delta) assert_equal(9.05, 9.05.to_d.to_f) - assert_equal("9.050000000000001", 9.05.to_d.to_s('F')) + assert_equal("9.05", 9.05.to_d.to_s('F')) assert_equal(Math::PI, Math::PI.to_d.to_f) @@ -34,6 +34,8 @@ def test_Float_to_d_without_precision assert_raise(TypeError) { 0.3.to_d(false) } assert(1.1.to_d.frozen?) + + assert_equal(BigDecimal("999_999.9999"), 999_999.9999.to_d) end def test_Float_to_d_with_precision From af6502c2606b9462fe21300243c604f4fd063356 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 13 Jan 2021 11:42:46 +0900 Subject: [PATCH 071/144] Changes: Add 3.0.1 entry --- CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 015e7d35..5132d6e3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,21 @@ # CHANGES +## 3.0.1 + +* Improve the conversion speed of BigDecimal() and to_d methods. + + **Kenta Murata** + +* Permit 0 digits in BigDecimal(float) and Float#to_d. + It means auto-detection of the smallest number of digits to represent + the given Float number without error. + + **Kenta Murata** + +* Fix precision issue of Float [GH-70] + + Reported by @casperisfine + ## 3.0.0 * Deprecate `BigDecimal#precs`. From f6765b80711c771f08b0fea3c6440d5cd89517ff Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:23:03 +0900 Subject: [PATCH 072/144] Suppress warning at NO_SANITIZE on gcc --- ext/bigdecimal/missing.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ext/bigdecimal/missing.c b/ext/bigdecimal/missing.c index b0bc6eea..703232d9 100644 --- a/ext/bigdecimal/missing.c +++ b/ext/bigdecimal/missing.c @@ -8,6 +8,16 @@ # define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) #endif +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +/* GCC warns about unknown sanitizer, which is annoying. */ +# undef NO_SANITIZE +# define NO_SANITIZE(x, y) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ + __attribute__((__no_sanitize__(x))) y; \ + _Pragma("GCC diagnostic pop") +#endif + #undef strtod #define strtod BigDecimal_strtod #undef dtoa From f0d94e68439afd45ffb8d5faeb8ef6b7dfbc4df4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:23:26 +0900 Subject: [PATCH 073/144] Explicit cast uint64_t to double --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e352f381..b00fb3e7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2704,7 +2704,7 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len = (size_t)ceil(log10(uval) / BASE_FIG); + const size_t len = (size_t)ceil(log10((double)uval) / BASE_FIG); vp = VpAllocReal(len); vp->MaxPrec = len; From b1f1ed26c93b774d706cc7523eb864cfd5645d5a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:26:15 +0900 Subject: [PATCH 074/144] Explicitly cast size_t to int --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b00fb3e7..e4dc5658 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2833,7 +2833,7 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; - char *p = BigDecimal_dtoa(d, mode, digs, &decpt, &negative_p, &e); + char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); if (len10 >= (int)sizeof(buf)) len10 = (int)sizeof(buf) - 1; From 11025811b71e93bd81daed5a320d33cf4e152fe7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 14 Jan 2021 09:47:33 +0900 Subject: [PATCH 075/144] Change the workflow step order --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 985e0ec8..71833339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,8 @@ jobs: - run: rake compile - - run: rake build - - run: rake test + - run: rake build + - run: gem install pkg/*.gem From 1e03da707692990fdc7f620fbeded22d49023537 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:00:24 +0900 Subject: [PATCH 076/144] Reorder the arguments of BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e4dc5658..6e808f13 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1345,7 +1345,7 @@ BigDecimal_mult(VALUE self, VALUE r) } static VALUE -BigDecimal_divide(Real **c, Real **res, Real **div, VALUE self, VALUE r) +BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) /* For c = self.div(r): with round operation */ { ENTER(5); @@ -1392,7 +1392,7 @@ BigDecimal_div(VALUE self, VALUE r) { ENTER(5); Real *c=NULL, *res=NULL, *div = NULL; - r = BigDecimal_divide(&c, &res, &div, self, r); + r = BigDecimal_divide(self, r, &c, &res, &div); if (!NIL_P(r)) return r; /* coerced by other */ SAVE(c); SAVE(res); SAVE(div); /* a/b = c + r/b */ From 3b55ad1c429ce88152bca6de6e2b88c4b2fb21f4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:19:39 +0900 Subject: [PATCH 077/144] Use new conversion functions in BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6e808f13..d6097a8f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -209,6 +209,7 @@ static VALUE rb_inum_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exc static VALUE rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_rational_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static VALUE rb_cstr_convert_to_BigDecimal(const char *c_str, size_t digs, int raise_exception); +static VALUE rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception); static Real* GetVpValueWithPrec(VALUE v, long prec, int must) @@ -1352,18 +1353,25 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + SAVE(a); + + VALUE rr = Qnil; if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); - } + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); + } else { - b = GetVpValue(r, 0); + rr = rb_convert_to_BigDecimal(r, 0, false); + } + + if (!is_kind_of_BigDecimal(rr)) { + return DoSomeOne(self, r, '/'); } - if (!b) return DoSomeOne(self, r, '/'); + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); *div = b; From 74cd0393df52b0b7a5afc99acbbd1325aa402607 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:21:12 +0900 Subject: [PATCH 078/144] Use larger precision in divide for irrational or recurring results Just in case for irrational or recurring results, the precision of the quotient is set to at least more than 2*Float::DIG plus alpha. [Bug #13754] [Fix GH-94] --- ext/bigdecimal/bigdecimal.c | 13 ++++++------- test/bigdecimal/test_bigdecimal.rb | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index d6097a8f..f314d26c 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1375,13 +1375,12 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = a->Prec + vabs(a->exponent); - if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx++; /* NOTE: An additional digit is needed for the compatibility to - the version 1.2.1 and the former. */ - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*DBLE_FIG > mx) + mx = 2*DBLE_FIG; + GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b015f9a9..7ebc66b2 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,9 +936,11 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_not_equal(BigDecimal('1487.0'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') - assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') + assert_in_delta(4.124045235, + (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), + 10**Float::MIN_10_EXP, '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -952,6 +954,15 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } end + def test_dev_precision + bug13754 = '[ruby-core:82107] [Bug #13754]' + a = BigDecimal('101') + b = BigDecimal('0.9163472602589686') + c = a/b + assert(c.precision > b.precision, + "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 2e1dfd23cb6e0a23cd0f15a9e9cc118e3d7fa501 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:41:18 +0900 Subject: [PATCH 079/144] Fix assersion for [Bug #9316] --- test/bigdecimal/test_bigdecimal.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 7ebc66b2..f9e912cf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,7 +936,9 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_not_equal(BigDecimal('1487.0'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), + (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), + '[ruby-core:59365] [#9316]') assert_in_delta(4.124045235, (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), From a109d0984fff46bc8535d7e03b225a968be62ec9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 06:57:30 +0900 Subject: [PATCH 080/144] [Doc] Fix the comment of BigDecimal_div2 [ci skip] --- ext/bigdecimal/bigdecimal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f314d26c..47b64043 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1617,7 +1617,8 @@ BigDecimal_divmod(VALUE self, VALUE r) } /* - * See BigDecimal#quo + * Do the same manner as Float#div when n is nil. + * Do the same manner as BigDecimal#quo when n is 0. */ static inline VALUE BigDecimal_div2(VALUE self, VALUE b, VALUE n) From 68c20200d5f4242ac8c4d2d69685c197140a5390 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:03:56 +0900 Subject: [PATCH 081/144] Use new conversion functions in BigDecimal_DoDivmod --- ext/bigdecimal/bigdecimal.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 47b64043..0e8400e4 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1425,18 +1425,25 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) Real *a, *b; size_t mx; - GUARD_OBJ(a, GetVpValue(self, 1)); + TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); + SAVE(a); + + VALUE rr = Qnil; if (RB_TYPE_P(r, T_FLOAT)) { - b = GetVpValueWithPrec(r, 0, 1); + rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { - b = GetVpValueWithPrec(r, a->Prec*VpBaseFig(), 1); + rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } else { - b = GetVpValue(r, 0); + rr = rb_convert_to_BigDecimal(r, 0, false); } - if (!b) return Qfalse; + if (!is_kind_of_BigDecimal(rr)) { + return Qfalse; + } + + TypedData_Get_Struct(rr, Real, &BigDecimal_data_type, b); SAVE(b); if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; From d2746121cfc57a151179da2266768f728b554a65 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:04:48 +0900 Subject: [PATCH 082/144] Use pre-allocated special values in BigDecimal_DoDivmod --- ext/bigdecimal/bigdecimal.c | 47 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0e8400e4..a7f9f351 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1449,28 +1449,32 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) if (VpIsNaN(a) || VpIsNaN(b)) goto NaN; if (VpIsInf(a) && VpIsInf(b)) goto NaN; if (VpIsZero(b)) { - rb_raise(rb_eZeroDivError, "divided by 0"); + rb_raise(rb_eZeroDivError, "divided by 0"); } if (VpIsInf(a)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - VpSetInf(d, (SIGNED_VALUE)(VpGetSign(a) == VpGetSign(b) ? 1 : -1)); - GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); - *div = d; - *mod = c; - return Qtrue; + if (VpGetSign(a) == VpGetSign(b)) { + VALUE inf = BigDecimal_positive_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + else { + VALUE inf = BigDecimal_negative_infinity(); + TypedData_Get_Struct(inf, Real, &BigDecimal_data_type, *div); + } + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + return Qtrue; } if (VpIsInf(b)) { - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - *div = d; - *mod = a; - return Qtrue; + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + *mod = a; + return Qtrue; } if (VpIsZero(a)) { - GUARD_OBJ(c, VpCreateRbObject(1, "0", true)); - GUARD_OBJ(d, VpCreateRbObject(1, "0", true)); - *div = d; - *mod = c; - return Qtrue; + VALUE zero = BigDecimal_positive_zero(); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(zero, Real, &BigDecimal_data_type, *mod); + return Qtrue; } mx = a->Prec + vabs(a->exponent); @@ -1496,11 +1500,12 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) } return Qtrue; -NaN: - GUARD_OBJ(c, VpCreateRbObject(1, "NaN", true)); - GUARD_OBJ(d, VpCreateRbObject(1, "NaN", true)); - *div = d; - *mod = c; + NaN: + { + VALUE nan = BigDecimal_nan(); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *div); + TypedData_Get_Struct(nan, Real, &BigDecimal_data_type, *mod); + } return Qtrue; } From b2cf1c134a47683d85fcbef9d1632faccc220983 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:10:54 +0900 Subject: [PATCH 083/144] Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++++++++------------ test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7f9f351..baa26200 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1477,26 +1477,33 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = a->Prec + vabs(a->exponent); - if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*DBLE_FIG > mx) + mx = 2*DBLE_FIG; + + GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); + GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(c, res, a, b); - mx = c->Prec * (VpBaseFig() + 1); + + mx = c->Prec * BASE_FIG; GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); + VpMult(res, d, b); VpAddSub(c, a, res, -1); + if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - VpAddSub(res, d, VpOne(), -1); + /* remainder adjustment for negative case */ + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f9e912cf..5df70bc6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1017,6 +1017,13 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_precision + a = BigDecimal('2e55') + b = BigDecimal('1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 716062b5014a8da9a14c52efb2a959e1a3674dc9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:25:37 +0900 Subject: [PATCH 084/144] Fix the precision of the adjusted quotient --- ext/bigdecimal/bigdecimal.c | 6 ++++-- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index baa26200..b095ec55 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1494,9 +1494,11 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ + /* result adjustment for negative case */ + res = VpReallocReal(res, d->MaxPrec); + res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5df70bc6..a343c8a4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1022,6 +1022,10 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) + + b = BigDecimal('-1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) end def test_add_bigdecimal From 1cb92487f79f1976d2526ebf6bbfb83872c45837 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 15 Jan 2021 10:44:45 +0900 Subject: [PATCH 085/144] Fix for the coerce cases in divide and DoDivmod --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b095ec55..4a30355f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,16 +1356,19 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1428,16 +1431,19 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a343c8a4..14e35bee 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -974,6 +974,15 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end + def test_div_with_complex + q = BigDecimal("3") / 1i + assert_kind_of(Complex, q) + end + + def test_div_error + assert_raise(TypeError) { BigDecimal(20) / '2' } + end + def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1028,6 +1037,10 @@ def test_divmod_precision assert_equal((a/b), q) end + def test_divmod_error + assert_raise(TypeError) { BigDecimal(20).divmod('2') } + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 3d1838ca55778b4724ee6ad841404e9b46d440f9 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:09:15 +0900 Subject: [PATCH 086/144] Revert "Fix for the coerce cases in divide and DoDivmod" This reverts commit 1cb92487f79f1976d2526ebf6bbfb83872c45837. --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++---------------- test/bigdecimal/test_bigdecimal.rb | 13 ------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 4a30355f..b095ec55 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,19 +1356,16 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = Qnil; + if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } + else { + rr = rb_convert_to_BigDecimal(r, 0, false); + } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1431,19 +1428,16 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = r; - if (is_kind_of_BigDecimal(rr)) { - /* do nothing */ - } - else if (RB_INTEGER_TYPE_P(r)) { - rr = rb_inum_convert_to_BigDecimal(r, 0, true); - } - else if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = Qnil; + if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } + else { + rr = rb_convert_to_BigDecimal(r, 0, false); + } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 14e35bee..a343c8a4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -974,15 +974,6 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end - def test_div_with_complex - q = BigDecimal("3") / 1i - assert_kind_of(Complex, q) - end - - def test_div_error - assert_raise(TypeError) { BigDecimal(20) / '2' } - end - def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1037,10 +1028,6 @@ def test_divmod_precision assert_equal((a/b), q) end - def test_divmod_error - assert_raise(TypeError) { BigDecimal(20).divmod('2') } - end - def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 20a2a877bf0e30d7b31692cbec2e3ac7b325f08d Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:09:41 +0900 Subject: [PATCH 087/144] Revert "Fix the precision of the adjusted quotient" This reverts commit 716062b5014a8da9a14c52efb2a959e1a3674dc9. --- ext/bigdecimal/bigdecimal.c | 6 ++---- test/bigdecimal/test_bigdecimal.rb | 4 ---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b095ec55..baa26200 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1494,11 +1494,9 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* result adjustment for negative case */ - res = VpReallocReal(res, d->MaxPrec); - res->MaxPrec = d->MaxPrec; + /* remainder adjustment for negative case */ VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index a343c8a4..5df70bc6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1022,10 +1022,6 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) - - b = BigDecimal('-1.23456789e10') - q, r = a.divmod(b) - assert_equal((a/b), q) end def test_add_bigdecimal From 5a6242820a2bd6c1043f405de5d13b0f847e0bea Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:10:11 +0900 Subject: [PATCH 088/144] Revert "Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide" This reverts commit b2cf1c134a47683d85fcbef9d1632faccc220983. --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++------------------ test/bigdecimal/test_bigdecimal.rb | 7 ------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index baa26200..a7f9f351 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1477,33 +1477,26 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; - if (2*DBLE_FIG > mx) - mx = 2*DBLE_FIG; - - GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); - GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + mx = a->Prec + vabs(a->exponent); + if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); + mx = (mx + 1) * VpBaseFig(); + GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); + GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(c, res, a, b); - - mx = c->Prec * BASE_FIG; + mx = c->Prec * (VpBaseFig() + 1); GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); - VpMult(res, d, b); VpAddSub(c, a, res, -1); - if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ - VpAddSub(res, d, VpOne(), -1); + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } - else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5df70bc6..f9e912cf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1017,13 +1017,6 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end - def test_divmod_precision - a = BigDecimal('2e55') - b = BigDecimal('1.23456789e10') - q, r = a.divmod(b) - assert_equal((a/b), q) - end - def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 12cfd9a2ced2128bc8b13c072c5df6bed8771fe3 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 20 Jan 2021 17:12:57 +0900 Subject: [PATCH 089/144] Revert "Use larger precision in divide for irrational or recurring results" This reverts commit 74cd0393df52b0b7a5afc99acbbd1325aa402607. [Reopen GH-94] --- ext/bigdecimal/bigdecimal.c | 13 +++++++------ test/bigdecimal/test_bigdecimal.rb | 17 ++--------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a7f9f351..910e1f9f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1375,12 +1375,13 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; - mx *= BASE_FIG; - if (2*DBLE_FIG > mx) - mx = 2*DBLE_FIG; - GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); + mx = a->Prec + vabs(a->exponent); + if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); + mx++; /* NOTE: An additional digit is needed for the compatibility to + the version 1.2.1 and the former. */ + mx = (mx + 1) * VpBaseFig(); + GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f9e912cf..b015f9a9 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -936,13 +936,9 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), - (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), - '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') - assert_in_delta(4.124045235, - (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), - 10**Float::MIN_10_EXP, '[#9305]') + assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -956,15 +952,6 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } end - def test_dev_precision - bug13754 = '[ruby-core:82107] [Bug #13754]' - a = BigDecimal('101') - b = BigDecimal('0.9163472602589686') - c = a/b - assert(c.precision > b.precision, - "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") - end - def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 84f135866b600a78d6015ef1c7ada87da58ee48a Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 21 Jan 2021 14:02:30 +0900 Subject: [PATCH 090/144] Revert "Revert "Fix for the coerce cases in divide and DoDivmod"" This reverts commit 3d1838ca55778b4724ee6ad841404e9b46d440f9. --- ext/bigdecimal/bigdecimal.c | 26 ++++++++++++++++---------- test/bigdecimal/test_bigdecimal.rb | 13 +++++++++++++ 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 910e1f9f..c553e1d7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1356,16 +1356,19 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return DoSomeOne(self, r, '/'); @@ -1429,16 +1432,19 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) TypedData_Get_Struct(self, Real, &BigDecimal_data_type, a); SAVE(a); - VALUE rr = Qnil; - if (RB_TYPE_P(r, T_FLOAT)) { + VALUE rr = r; + if (is_kind_of_BigDecimal(rr)) { + /* do nothing */ + } + else if (RB_INTEGER_TYPE_P(r)) { + rr = rb_inum_convert_to_BigDecimal(r, 0, true); + } + else if (RB_TYPE_P(r, T_FLOAT)) { rr = rb_float_convert_to_BigDecimal(r, 0, true); } else if (RB_TYPE_P(r, T_RATIONAL)) { rr = rb_rational_convert_to_BigDecimal(r, a->Prec*BASE_FIG, true); } - else { - rr = rb_convert_to_BigDecimal(r, 0, false); - } if (!is_kind_of_BigDecimal(rr)) { return Qfalse; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index b015f9a9..49e6b2a7 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -961,6 +961,15 @@ def test_div_with_rational assert_kind_of(BigDecimal, BigDecimal("3") / 1.quo(3)) end + def test_div_with_complex + q = BigDecimal("3") / 1i + assert_kind_of(Complex, q) + end + + def test_div_error + assert_raise(TypeError) { BigDecimal(20) / '2' } + end + def test_mod x = BigDecimal((2**100).to_s) assert_equal(1, x % 3) @@ -1004,6 +1013,10 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_error + assert_raise(TypeError) { BigDecimal(20).divmod('2') } + end + def test_add_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(3000000000000000000000000000000, x.add(x, 1)) From 4d5b97125b8edc71feae45653d8dd4663dcc38c6 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 29 Jan 2021 18:06:13 +0900 Subject: [PATCH 091/144] Fix the maximum length of float number This change is for preventing the false-positive alert by CoverityScan. See CID-1471770 for the detail. --- ext/bigdecimal/bigdecimal.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c553e1d7..e0832b82 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2856,14 +2856,16 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) } /* Use the same logic in flo_to_s to convert a float to a decimal string */ - char buf[DBLE_FIG + BASE_FIG + 2 + 1]; + char buf[DBLE_FIG + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); - if (len10 >= (int)sizeof(buf)) - len10 = (int)sizeof(buf) - 1; + if (len10 > DBLE_FIG) { + /* TODO: Presumably, rounding should be done here. */ + len10 = DBLE_FIG; + } memcpy(buf, p, len10); xfree(p); From 7479923fdba7dbf9c759f29959e1c165146a7365 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 30 Jan 2021 12:05:15 +0900 Subject: [PATCH 092/144] Stop using rmpd and RMPD prefixes and DBLE_FIG --- ext/bigdecimal/bigdecimal.c | 90 ++++++++++++++++++------------------- ext/bigdecimal/bigdecimal.h | 55 +++++++++-------------- 2 files changed, 64 insertions(+), 81 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0832b82..b9ba0ea6 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -67,16 +67,12 @@ static ID id_half; #define SAVE(p) PUSH((p)->obj) #define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) -#define BASE_FIG RMPD_COMPONENT_FIGURES -#define BASE RMPD_BASE +#define BASE_FIG BIGDECIMAL_COMPONENT_FIGURES +#define BASE BIGDECIMAL_BASE #define HALF_BASE (BASE/2) #define BASE1 (BASE/10) -#ifndef DBLE_FIG -#define DBLE_FIG RMPD_DOUBLE_FIGURES /* figure of double */ -#endif - #define LOG10_2 0.3010299956639812 #ifndef RRATIONAL_ZERO_P @@ -2371,7 +2367,7 @@ is_even(VALUE x) } static VALUE -rmpd_power_by_big_decimal(Real const* x, Real const* exp, ssize_t const n) +bigdecimal_power_by_bigdecimal(Real const* x, Real const* exp, ssize_t const n) { VALUE log_x, multiplied, y; volatile VALUE obj = exp->obj; @@ -2441,7 +2437,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) goto retry; } if (NIL_P(prec)) { - n += DBLE_FIG; + n += BIGDECIMAL_DOUBLE_FIGURES; } exp = GetVpValueWithPrec(vexp, 0, 1); break; @@ -2573,7 +2569,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) } if (exp != NULL) { - return rmpd_power_by_big_decimal(x, exp, n); + return bigdecimal_power_by_bigdecimal(x, exp, n); } else if (RB_TYPE_P(vexp, T_BIGNUM)) { VALUE abs_value = BigDecimal_abs(self); @@ -2630,7 +2626,7 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) else { GUARD_OBJ(y, VpCreateRbObject(1, "0", true)); } - VpPower(y, x, int_exp); + VpPowerByInt(y, x, int_exp); if (!NIL_P(prec) && VpIsDef(y)) { VpMidRound(y, VpGetRoundMode(), n); } @@ -2849,22 +2845,22 @@ rb_float_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) "can't omit precision for a %"PRIsVALUE".", CLASS_OF(val)); } - else if (digs > DBLE_FIG) { + else if (digs > BIGDECIMAL_DOUBLE_FIGURES) { if (!raise_exception) return Qnil; rb_raise(rb_eArgError, "precision too large."); } /* Use the same logic in flo_to_s to convert a float to a decimal string */ - char buf[DBLE_FIG + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ + char buf[BIGDECIMAL_DOUBLE_FIGURES + BASE_FIG + 2 + 1]; /* sizeof(buf) == 28 in the typical case */ int decpt, negative_p; char *e; const int mode = digs == 0 ? 0 : 2; char *p = BigDecimal_dtoa(d, mode, (int)digs, &decpt, &negative_p, &e); int len10 = (int)(e - p); - if (len10 > DBLE_FIG) { + if (len10 > BIGDECIMAL_DOUBLE_FIGURES) { /* TODO: Presumably, rounding should be done here. */ - len10 = DBLE_FIG; + len10 = BIGDECIMAL_DOUBLE_FIGURES; } memcpy(buf, p, len10); xfree(p); @@ -3381,7 +3377,7 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) } x = vx->obj; - n = prec + rmpd_double_figures(); + n = prec + BIGDECIMAL_DOUBLE_FIGURES; negative = BIGDECIMAL_NEGATIVE_P(vx); if (negative) { VALUE x_zero = INT2NUM(1); @@ -3406,8 +3402,8 @@ BigMath_s_exp(VALUE klass, VALUE x, VALUE vprec) if (m <= 0) { break; } - else if ((size_t)m < rmpd_double_figures()) { - m = rmpd_double_figures(); + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; } d = BigDecimal_mult(d, x); /* d <- d * x */ @@ -3543,7 +3539,7 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) RB_GC_GUARD(one) = VpCheckGetValue(VpCreateRbObject(1, "1", true)); RB_GC_GUARD(two) = VpCheckGetValue(VpCreateRbObject(1, "2", true)); - n = prec + rmpd_double_figures(); + n = prec + BIGDECIMAL_DOUBLE_FIGURES; RB_GC_GUARD(vn) = SSIZET2NUM(n); expo = VpExponent10(vx); if (expo < 0 || expo >= 3) { @@ -3567,8 +3563,8 @@ BigMath_s_log(VALUE klass, VALUE x, VALUE vprec) if (m <= 0) { break; } - else if ((size_t)m < rmpd_double_figures()) { - m = rmpd_double_figures(); + else if ((size_t)m < BIGDECIMAL_DOUBLE_FIGURES) { + m = BIGDECIMAL_DOUBLE_FIGURES; } x = BigDecimal_mult2(x2, x, vn); @@ -4095,7 +4091,7 @@ VpFree(Real *pv) * EXCEPTION Handling. */ -#define rmpd_set_thread_local_exception_mode(mode) \ +#define bigdecimal_set_thread_local_exception_mode(mode) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_exception_mode, \ @@ -4111,8 +4107,8 @@ VpGetException (void) ); if (NIL_P(vmode)) { - rmpd_set_thread_local_exception_mode(RMPD_EXCEPTION_MODE_DEFAULT); - return RMPD_EXCEPTION_MODE_DEFAULT; + bigdecimal_set_thread_local_exception_mode(BIGDECIMAL_EXCEPTION_MODE_DEFAULT); + return BIGDECIMAL_EXCEPTION_MODE_DEFAULT; } return NUM2USHORT(vmode); @@ -4121,20 +4117,20 @@ VpGetException (void) static void VpSetException(unsigned short f) { - rmpd_set_thread_local_exception_mode(f); + bigdecimal_set_thread_local_exception_mode(f); } /* * Precision limit. */ -#define rmpd_set_thread_local_precision_limit(limit) \ +#define bigdecimal_set_thread_local_precision_limit(limit) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_precision_limit, \ SIZET2NUM(limit) \ ) -#define RMPD_PRECISION_LIMIT_DEFAULT ((size_t)0) +#define BIGDECIMAL_PRECISION_LIMIT_DEFAULT ((size_t)0) /* These 2 functions added at v1.1.7 */ VP_EXPORT size_t @@ -4146,8 +4142,8 @@ VpGetPrecLimit(void) ); if (NIL_P(vlimit)) { - rmpd_set_thread_local_precision_limit(RMPD_PRECISION_LIMIT_DEFAULT); - return RMPD_PRECISION_LIMIT_DEFAULT; + bigdecimal_set_thread_local_precision_limit(BIGDECIMAL_PRECISION_LIMIT_DEFAULT); + return BIGDECIMAL_PRECISION_LIMIT_DEFAULT; } return NUM2SIZET(vlimit); @@ -4157,7 +4153,7 @@ VP_EXPORT size_t VpSetPrecLimit(size_t n) { size_t const s = VpGetPrecLimit(); - rmpd_set_thread_local_precision_limit(n); + bigdecimal_set_thread_local_precision_limit(n); return s; } @@ -4165,7 +4161,7 @@ VpSetPrecLimit(size_t n) * Rounding mode. */ -#define rmpd_set_thread_local_rounding_mode(mode) \ +#define bigdecimal_set_thread_local_rounding_mode(mode) \ rb_thread_local_aset( \ rb_thread_current(), \ id_BigDecimal_rounding_mode, \ @@ -4181,8 +4177,8 @@ VpGetRoundMode(void) ); if (NIL_P(vmode)) { - rmpd_set_thread_local_rounding_mode(RMPD_ROUNDING_MODE_DEFAULT); - return RMPD_ROUNDING_MODE_DEFAULT; + bigdecimal_set_thread_local_rounding_mode(BIGDECIMAL_ROUNDING_MODE_DEFAULT); + return BIGDECIMAL_ROUNDING_MODE_DEFAULT; } return NUM2USHORT(vmode); @@ -4210,7 +4206,7 @@ VP_EXPORT unsigned short VpSetRoundMode(unsigned short n) { if (VpIsRoundMode(n)) { - rmpd_set_thread_local_rounding_mode(n); + bigdecimal_set_thread_local_rounding_mode(n); return n; } @@ -4450,7 +4446,7 @@ VpNumOfChars(Real *vp,const char *pszFmt) * by one DECDIG word in the computer used. * * [Returns] - * DBLE_FIG ... OK + * BIGDECIMAL_DOUBLE_FIGURES ... OK */ VP_EXPORT size_t VpInit(DECDIG BaseVal) @@ -4468,16 +4464,16 @@ VpInit(DECDIG BaseVal) #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); + printf("VpInit: BaseVal = %"PRIuDECDIG"\n", BaseVal); printf("\tBASE = %"PRIuDECDIG"\n", BASE); printf("\tHALF_BASE = %"PRIuDECDIG"\n", HALF_BASE); printf("\tBASE1 = %"PRIuDECDIG"\n", BASE1); printf("\tBASE_FIG = %u\n", BASE_FIG); - printf("\tDBLE_FIG = %d\n", DBLE_FIG); + printf("\tBIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); } #endif /* BIGDECIMAL_DEBUG */ - return rmpd_double_figures(); + return BIGDECIMAL_DOUBLE_FIGURES; } VP_EXPORT Real * @@ -4525,7 +4521,7 @@ AddExponent(Real *a, SIGNED_VALUE n) } Real * -rmpd_parse_special_string(const char *str) +bigdecimal_parse_special_string(const char *str) { static const struct { const char *str; @@ -4626,7 +4622,7 @@ VpAlloc(size_t mx, const char *szVal, int strict_p, int exc) } /* Check on Inf & NaN */ - if ((vp = rmpd_parse_special_string(szVal)) != NULL) { + if ((vp = bigdecimal_parse_special_string(szVal)) != NULL) { return vp; } @@ -5832,7 +5828,7 @@ VPrint(FILE *fp, const char *cntl_chr, Real *a) case '0': case 'z': ZeroSup = 0; ++j; - sep = cntl_chr[j] == 'z' ? RMPD_COMPONENT_FIGURES : 10; + sep = cntl_chr[j] == 'z' ? BIGDECIMAL_COMPONENT_FIGURES : 10; break; } for (i = 0; i < a->Prec; ++i) { @@ -6284,7 +6280,7 @@ VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, con * [Output] * *d ... fraction part of m(d = 0.xxxxxxx). where # of 'x's is fig. * *e ... exponent of m. - * DBLE_FIG ... Number of digits in a double variable. + * BIGDECIMAL_DOUBLE_FIGURES ... Number of digits in a double variable. * * m -> d*10**e, 0Prec); *d = 0.0; @@ -6348,7 +6344,7 @@ VpVtoD(double *d, SIGNED_VALUE *e, Real *m) if (gfDebug) { VPrint(stdout, " VpVtoD: m=%\n", m); printf(" d=%e * 10 **%ld\n", *d, *e); - printf(" DBLE_FIG = %d\n", DBLE_FIG); + printf(" BIGDECIMAL_DOUBLE_FIGURES = %d\n", BIGDECIMAL_DOUBLE_FIGURES); } #endif /*BIGDECIMAL_DEBUG */ return f; @@ -6549,7 +6545,7 @@ VpSqrt(Real *y, Real *x) } VpDtoV(y, sqrt(val)); /* y <- sqrt(val) */ y->exponent += n; - n = (SIGNED_VALUE)((DBLE_FIG + BASE_FIG - 1) / BASE_FIG); + n = (SIGNED_VALUE)roomof(BIGDECIMAL_DOUBLE_FIGURES, BASE_FIG); y->MaxPrec = Min((size_t)n , y_prec); f->MaxPrec = y->MaxPrec + 1; n = (SIGNED_VALUE)(y_prec * BASE_FIG); @@ -6919,7 +6915,7 @@ VpFrac(Real *y, Real *x) * y = x ** n */ VP_EXPORT int -VpPower(Real *y, Real *x, SIGNED_VALUE n) +VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n) { size_t s, ss; ssize_t sign; @@ -7006,8 +7002,8 @@ VpPower(Real *y, Real *x, SIGNED_VALUE n) Exit: #ifdef BIGDECIMAL_DEBUG if (gfDebug) { - VPrint(stdout, "VpPower y=%\n", y); - VPrint(stdout, "VpPower x=%\n", x); + VPrint(stdout, "VpPowerByInt y=%\n", y); + VPrint(stdout, "VpPowerByInt x=%\n", x); printf(" n=%"PRIdVALUE"\n", n); } #endif /* BIGDECIMAL_DEBUG */ diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index 5f343db6..acc00b81 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -24,9 +24,9 @@ # define SIZEOF_DECDIG 4 # define PRI_DECDIG_PREFIX "" # ifdef PRI_LL_PREFIX -# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX +# define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX # else -# define PRI_DECDIG_DBL_PREFIX "l" +# define PRI_DECDIG_DBL_PREFIX "l" # endif #else # define DECDIG uint16_t @@ -51,6 +51,18 @@ #define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" #define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" +#if SIZEOF_DECDIG == 4 +# define BIGDECIMAL_BASE ((DECDIG)1000000000U) +# define BIGDECIMAL_COMPONENT_FIGURES 9 +#elif SIZEOF_DECDIG == 2 +# define BIGDECIMAL_BASE ((DECDIG)10000U) +# define BIGDECIMAL_COMPONENT_FIGURES 4 +#else +# error Unknown size of DECDIG +#endif + +#define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG) + #if defined(__cplusplus) extern "C" { #if 0 @@ -60,25 +72,6 @@ extern "C" { extern VALUE rb_cBigDecimal; -#if 0 || SIZEOF_DECDIG >= 16 -# define RMPD_COMPONENT_FIGURES 38 -# define RMPD_BASE ((DECDIG)100000000000000000000000000000000000000U) -#elif SIZEOF_DECDIG >= 8 -# define RMPD_COMPONENT_FIGURES 19 -# define RMPD_BASE ((DECDIG)10000000000000000000U) -#elif SIZEOF_DECDIG >= 4 -# define RMPD_COMPONENT_FIGURES 9 -# define RMPD_BASE ((DECDIG)1000000000U) -#elif SIZEOF_DECDIG >= 2 -# define RMPD_COMPONENT_FIGURES 4 -# define RMPD_BASE ((DECDIG)10000U) -#else -# define RMPD_COMPONENT_FIGURES 2 -# define RMPD_BASE ((DECDIG)100U) -#endif - -#define RMPD_DOUBLE_FIGURES (1+DBL_DIG) - /* * NaN & Infinity */ @@ -104,7 +97,7 @@ extern VALUE rb_cBigDecimal; /* Following 2 exceptions can't controlled by user */ #define VP_EXCEPTION_OP ((unsigned short)0x0020) -#define RMPD_EXCEPTION_MODE_DEFAULT 0U +#define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U /* Computation mode */ #define VP_ROUND_MODE ((unsigned short)0x0100) @@ -116,7 +109,7 @@ extern VALUE rb_cBigDecimal; #define VP_ROUND_FLOOR 6 #define VP_ROUND_HALF_EVEN 7 -#define RMPD_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP +#define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP #define VP_SIGN_NaN 0 /* NaN */ #define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ @@ -171,16 +164,9 @@ VP_EXPORT Real *VpNewRbClass(size_t mx, char const *str, VALUE klass, bool stric VP_EXPORT Real *VpCreateRbObject(size_t mx, const char *str, bool raise_exception); -static inline DECDIG -rmpd_base_value(void) { return RMPD_BASE; } -static inline size_t -rmpd_component_figures(void) { return RMPD_COMPONENT_FIGURES; } -static inline size_t -rmpd_double_figures(void) { return RMPD_DOUBLE_FIGURES; } - -#define VpBaseFig() rmpd_component_figures() -#define VpDblFig() rmpd_double_figures() -#define VpBaseVal() rmpd_base_value() +#define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES +#define VpDblFig() BIGDECIMAL_DOUBLE_FIGURES +#define VpBaseVal() BIGDECIMAL_BASE /* Zero,Inf,NaN (isinf(),isnan() used to check) */ VP_EXPORT double VpGetDoubleNaN(void); @@ -228,7 +214,8 @@ VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); VP_EXPORT void VpFrac(Real *y, Real *x); -VP_EXPORT int VpPower(Real *y, Real *x, SIGNED_VALUE n); +VP_EXPORT int VpPowerByInt(Real *y, Real *x, SIGNED_VALUE n); +#define VpPower VpPowerByInt /* VP constants */ VP_EXPORT Real *VpOne(void); From 9067b353accb4dc407488460ccd1228e519d9185 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Sat, 30 Jan 2021 13:00:03 +0900 Subject: [PATCH 093/144] Fix uint64 conversion Stop using logarithm to compute the number of components. Instead, use the theoretical maximum number of components for buffer, and count up the actual number of components during conversion. --- ext/bigdecimal/bigdecimal.c | 23 +++++++++++------------ ext/bigdecimal/bigdecimal.h | 16 ++++++++++++++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index b9ba0ea6..704f0451 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2727,23 +2727,22 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r vp->frac[0] = (DECDIG)uval; } else { - const size_t len = (size_t)ceil(log10((double)uval) / BASE_FIG); - - vp = VpAllocReal(len); - vp->MaxPrec = len; - vp->Prec = len; - vp->exponent = len; - VpSetSign(vp, 1); - - size_t i, ntz = 0; - for (i = 0; i < len; ++i) { + DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; + size_t exp = 0, ntz = 0; + for (; uval > 0; ++exp) { DECDIG r = uval % BASE; - vp->frac[len - i - 1] = r; if (r == 0) ++ntz; + buf[BIGDECIMAL_INT64_MAX_LENGTH - exp - 1] = r; uval /= BASE; } - vp->Prec -= ntz; + const size_t len = exp - ntz; + vp = VpAllocReal(len); + vp->MaxPrec = len; + vp->Prec = len; + vp->exponent = exp; + VpSetSign(vp, 1); + MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - exp, DECDIG, len); } return BigDecimal_wrap_struct(obj, vp); diff --git a/ext/bigdecimal/bigdecimal.h b/ext/bigdecimal/bigdecimal.h index acc00b81..bd1c4674 100644 --- a/ext/bigdecimal/bigdecimal.h +++ b/ext/bigdecimal/bigdecimal.h @@ -54,9 +54,25 @@ #if SIZEOF_DECDIG == 4 # define BIGDECIMAL_BASE ((DECDIG)1000000000U) # define BIGDECIMAL_COMPONENT_FIGURES 9 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 9_223372036_854775807 + * UINT64_MAX: 18_446744073_709551615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 3 + #elif SIZEOF_DECDIG == 2 # define BIGDECIMAL_BASE ((DECDIG)10000U) # define BIGDECIMAL_COMPONENT_FIGURES 4 +/* + * The number of components required for a 64-bit integer. + * + * INT64_MAX: 922_3372_0368_5477_5807 + * UINT64_MAX: 1844_6744_0737_0955_1615 + */ +# define BIGDECIMAL_INT64_MAX_LENGTH 5 + #else # error Unknown size of DECDIG #endif From ac62a6cc77f7758e9e51fcce0d4baa2eeebaec52 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 3 May 2021 12:25:16 +0900 Subject: [PATCH 094/144] Version 3.0.1 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index ef003e0e..2e017eee 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1.dev' +bigdecimal_version = '3.0.1' Gem::Specification.new do |s| s.name = "bigdecimal" From bff0911a8cea11251ff21128b2dbbafe8faab73a Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 20:56:44 +0900 Subject: [PATCH 095/144] Removed test-suite of ruby repository --- test/lib/envutil.rb | 365 ----------- test/lib/find_executable.rb | 22 - test/lib/test/unit.rb | 1003 ------------------------------ test/lib/test/unit/assertions.rb | 940 ---------------------------- test/lib/test/unit/parallel.rb | 191 ------ test/lib/test/unit/testcase.rb | 36 -- 6 files changed, 2557 deletions(-) delete mode 100644 test/lib/envutil.rb delete mode 100644 test/lib/find_executable.rb delete mode 100644 test/lib/test/unit.rb delete mode 100644 test/lib/test/unit/assertions.rb delete mode 100644 test/lib/test/unit/parallel.rb delete mode 100644 test/lib/test/unit/testcase.rb diff --git a/test/lib/envutil.rb b/test/lib/envutil.rb deleted file mode 100644 index 937e1128..00000000 --- a/test/lib/envutil.rb +++ /dev/null @@ -1,365 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true -require "open3" -require "timeout" -require_relative "find_executable" -begin - require 'rbconfig' -rescue LoadError -end -begin - require "rbconfig/sizeof" -rescue LoadError -end - -module EnvUtil - def rubybin - if ruby = ENV["RUBY"] - return ruby - end - ruby = "ruby" - exeext = RbConfig::CONFIG["EXEEXT"] - rubyexe = (ruby + exeext if exeext and !exeext.empty?) - 3.times do - if File.exist? ruby and File.executable? ruby and !File.directory? ruby - return File.expand_path(ruby) - end - if rubyexe and File.exist? rubyexe and File.executable? rubyexe - return File.expand_path(rubyexe) - end - ruby = File.join("..", ruby) - end - if defined?(RbConfig.ruby) - RbConfig.ruby - else - "ruby" - end - end - module_function :rubybin - - LANG_ENVS = %w"LANG LC_ALL LC_CTYPE" - - DEFAULT_SIGNALS = Signal.list - DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM - - RUBYLIB = ENV["RUBYLIB"] - - class << self - attr_accessor :timeout_scale - attr_reader :original_internal_encoding, :original_external_encoding, - :original_verbose, :original_warning - - def capture_global_values - @original_internal_encoding = Encoding.default_internal - @original_external_encoding = Encoding.default_external - @original_verbose = $VERBOSE - @original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil - end - end - - def apply_timeout_scale(t) - if scale = EnvUtil.timeout_scale - t * scale - else - t - end - end - module_function :apply_timeout_scale - - def timeout(sec, klass = nil, message = nil, &blk) - return yield(sec) if sec == nil or sec.zero? - sec = apply_timeout_scale(sec) - Timeout.timeout(sec, klass, message, &blk) - end - module_function :timeout - - def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1) - reprieve = apply_timeout_scale(reprieve) if reprieve - - signals = Array(signal).select do |sig| - DEFAULT_SIGNALS[sig.to_s] or - DEFAULT_SIGNALS[Signal.signame(sig)] rescue false - end - signals |= [:ABRT, :KILL] - case pgroup - when 0, true - pgroup = -pid - when nil, false - pgroup = pid - end - - lldb = true if /darwin/ =~ RUBY_PLATFORM - - while signal = signals.shift - - if lldb and [:ABRT, :KILL].include?(signal) - lldb = false - # sudo -n: --non-interactive - # lldb -p: attach - # -o: run command - system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit]) - true - end - - begin - Process.kill signal, pgroup - rescue Errno::EINVAL - next - rescue Errno::ESRCH - break - end - if signals.empty? or !reprieve - Process.wait(pid) - else - begin - Timeout.timeout(reprieve) {Process.wait(pid)} - rescue Timeout::Error - else - break - end - end - end - $? - end - module_function :terminate - - def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false, - encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error, - stdout_filter: nil, stderr_filter: nil, - signal: :TERM, - rubybin: EnvUtil.rubybin, precommand: nil, - **opt) - timeout = apply_timeout_scale(timeout) - - in_c, in_p = IO.pipe - out_p, out_c = IO.pipe if capture_stdout - err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout - opt[:in] = in_c - opt[:out] = out_c if capture_stdout - opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr - if encoding - out_p.set_encoding(encoding) if out_p - err_p.set_encoding(encoding) if err_p - end - c = "C" - child_env = {} - LANG_ENVS.each {|lc| child_env[lc] = c} - if Array === args and Hash === args.first - child_env.update(args.shift) - end - if RUBYLIB and lib = child_env["RUBYLIB"] - child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR) - end - child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS'] - args = [args] if args.kind_of?(String) - pid = spawn(child_env, *precommand, rubybin, *args, **opt) - in_c.close - out_c&.close - out_c = nil - err_c&.close - err_c = nil - if block_given? - return yield in_p, out_p, err_p, pid - else - th_stdout = Thread.new { out_p.read } if capture_stdout - th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout - in_p.write stdin_data.to_str unless stdin_data.empty? - in_p.close - if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout)) - timeout_error = nil - else - status = terminate(pid, signal, opt[:pgroup], reprieve) - terminated = Time.now - end - stdout = th_stdout.value if capture_stdout - stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout - out_p.close if capture_stdout - err_p.close if capture_stderr && capture_stderr != :merge_to_stdout - status ||= Process.wait2(pid)[1] - stdout = stdout_filter.call(stdout) if stdout_filter - stderr = stderr_filter.call(stderr) if stderr_filter - if timeout_error - bt = caller_locations - msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)" - msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n")) - raise timeout_error, msg, bt.map(&:to_s) - end - return stdout, stderr, status - end - ensure - [th_stdout, th_stderr].each do |th| - th.kill if th - end - [in_c, in_p, out_c, out_p, err_c, err_p].each do |io| - io&.close - end - [th_stdout, th_stderr].each do |th| - th.join if th - end - end - module_function :invoke_ruby - - def verbose_warning - class << (stderr = "".dup) - alias write concat - def flush; end - end - stderr, $stderr = $stderr, stderr - $VERBOSE = true - yield stderr - return $stderr - ensure - stderr, $stderr = $stderr, stderr - $VERBOSE = EnvUtil.original_verbose - EnvUtil.original_warning&.each {|i, v| Warning[i] = v} - end - module_function :verbose_warning - - def default_warning - $VERBOSE = false - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :default_warning - - def suppress_warning - $VERBOSE = nil - yield - ensure - $VERBOSE = EnvUtil.original_verbose - end - module_function :suppress_warning - - def under_gc_stress(stress = true) - stress, GC.stress = GC.stress, stress - yield - ensure - GC.stress = stress - end - module_function :under_gc_stress - - def with_default_external(enc) - suppress_warning { Encoding.default_external = enc } - yield - ensure - suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding } - end - module_function :with_default_external - - def with_default_internal(enc) - suppress_warning { Encoding.default_internal = enc } - yield - ensure - suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding } - end - module_function :with_default_internal - - def labeled_module(name, &block) - Module.new do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_module - - def labeled_class(name, superclass = Object, &block) - Class.new(superclass) do - singleton_class.class_eval { - define_method(:to_s) {name} - alias inspect to_s - alias name to_s - } - class_eval(&block) if block - end - end - module_function :labeled_class - - if /darwin/ =~ RUBY_PLATFORM - DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports") - DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S' - @ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME'] - - def self.diagnostic_reports(signame, pid, now) - return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame) - cmd = File.basename(rubybin) - cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd - path = DIAGNOSTIC_REPORTS_PATH - timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT - pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash" - first = true - 30.times do - first ? (first = false) : sleep(0.1) - Dir.glob(pat) do |name| - log = File.read(name) rescue next - if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log - File.unlink(name) - File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil - return log - end - end - end - nil - end - else - def self.diagnostic_reports(signame, pid, now) - end - end - - def self.failure_description(status, now, message = "", out = "") - pid = status.pid - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(?$()]/ ? s.inspect : s }.join " " - @options = options - end - - private - def setup_options(opts, options) - opts.separator 'minitest options:' - opts.version = MiniTest::Unit::VERSION - - opts.on '-h', '--help', 'Display this help.' do - puts opts - exit - end - - opts.on '-s', '--seed SEED', Integer, "Sets random seed" do |m| - options[:seed] = m - end - - opts.on '-v', '--verbose', "Verbose. Show progress processing files." do - options[:verbose] = true - self.verbose = options[:verbose] - end - - opts.on '-n', '--name PATTERN', "Filter test method names on pattern: /REGEXP/ or STRING" do |a| - options[:filter] = a - end - - opts.on '--test-order=random|alpha|sorted', [:random, :alpha, :sorted] do |a| - MiniTest::Unit::TestCase.test_order = a - end - end - - def non_options(files, options) - true - end - end - - module Parallel # :nodoc: all - def process_args(args = []) - return @options if @options - options = super - if @options[:parallel] - @files = args - end - options - end - - def status(*args) - result = super - raise @interrupt if @interrupt - result - end - - private - def setup_options(opts, options) - super - - opts.separator "parallel test options:" - - options[:retry] = true - - opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a| - if /^t/ =~ a - options[:testing] = true # For testing - options[:parallel] = a[1..-1].to_i - else - options[:parallel] = a.to_i - end - end - - opts.on '--separate', "Restart job process after one testcase has done" do - options[:parallel] ||= 1 - options[:separate] = true - end - - opts.on '--retry', "Retry running testcase when --jobs specified" do - options[:retry] = true - end - - opts.on '--no-retry', "Disable --retry" do - options[:retry] = false - end - - opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a| - options[:ruby] = a.split(/ /).reject(&:empty?) - end - end - - class Worker - def self.launch(ruby,args=[]) - io = IO.popen([*ruby, - "#{File.dirname(__FILE__)}/unit/parallel.rb", - *args], "rb+") - new(io, io.pid, :waiting) - end - - attr_reader :quit_called - - def initialize(io, pid, status) - @io = io - @pid = pid - @status = status - @file = nil - @real_file = nil - @loadpath = [] - @hooks = {} - @quit_called = false - end - - def puts(*args) - @io.puts(*args) - end - - def run(task,type) - @file = File.basename(task, ".rb") - @real_file = task - begin - puts "loadpath #{[Marshal.dump($:-@loadpath)].pack("m0")}" - @loadpath = $:.dup - puts "run #{task} #{type}" - @status = :prepare - rescue Errno::EPIPE - died - rescue IOError - raise unless ["stream closed","closed stream"].include? $!.message - died - end - end - - def hook(id,&block) - @hooks[id] ||= [] - @hooks[id] << block - self - end - - def read - res = (@status == :quit) ? @io.read : @io.gets - res && res.chomp - end - - def close - @io.close unless @io.closed? - self - rescue IOError - end - - def quit - return if @io.closed? - @quit_called = true - @io.puts "quit" - @io.close - end - - def kill - Process.kill(:KILL, @pid) - rescue Errno::ESRCH - end - - def died(*additional) - @status = :quit - @io.close - status = $? - if status and status.signaled? - additional[0] ||= SignalException.new(status.termsig) - end - - call_hook(:dead,*additional) - end - - def to_s - if @file - "#{@pid}=#{@file}" - else - "#{@pid}:#{@status.to_s.ljust(7)}" - end - end - - attr_reader :io, :pid - attr_accessor :status, :file, :real_file, :loadpath - - private - - def call_hook(id,*additional) - @hooks[id] ||= [] - @hooks[id].each{|hook| hook[self,additional] } - self - end - - end - - def after_worker_down(worker, e=nil, c=false) - return unless @options[:parallel] - return if @interrupt - warn e if e - @need_quit = true - warn "" - warn "Some worker was crashed. It seems ruby interpreter's bug" - warn "or, a bug of test/unit/parallel.rb. try again without -j" - warn "option." - warn "" - STDERR.flush - exit c - end - - def after_worker_quit(worker) - return unless @options[:parallel] - return if @interrupt - @workers.delete(worker) - @dead_workers << worker - @ios = @workers.map(&:io) - end - - def launch_worker - begin - worker = Worker.launch(@options[:ruby], @run_options) - rescue => e - abort "ERROR: Failed to launch job process - #{e.class}: #{e.message}" - end - worker.hook(:dead) do |w,info| - after_worker_quit w - after_worker_down w, *info if !info.empty? && !worker.quit_called - end - @workers << worker - @ios << worker.io - @workers_hash[worker.io] = worker - worker - end - - def delete_worker(worker) - @workers_hash.delete worker.io - @workers.delete worker - @ios.delete worker.io - end - - def quit_workers - return if @workers.empty? - @workers.reject! do |worker| - begin - Timeout.timeout(1) do - worker.quit - end - rescue Errno::EPIPE - rescue Timeout::Error - end - worker.close - end - - return if @workers.empty? - begin - Timeout.timeout(0.2 * @workers.size) do - Process.waitall - end - rescue Timeout::Error - @workers.each do |worker| - worker.kill - end - @worker.clear - end - end - - def deal(io, type, result, rep, shutting_down = false) - worker = @workers_hash[io] - cmd = worker.read - cmd.sub!(/\A\.+/, '') if cmd # read may return nil - case cmd - when '' - # just only dots, ignore - when /^okay$/ - worker.status = :running - jobs_status - when /^ready(!)?$/ - bang = $1 - worker.status = :ready - - return nil unless task = @tasks.shift - if @options[:separate] and not bang - worker.quit - worker = add_worker - end - worker.run(task, type) - @test_count += 1 - - jobs_status - when /^done (.+?)$/ - begin - r = Marshal.load($1.unpack("m")[0]) - rescue - print "unknown object: #{$1.unpack("m")[0].dump}" - return true - end - result << r[0..1] unless r[0..1] == [nil,nil] - rep << {file: worker.real_file, report: r[2], result: r[3], testcase: r[5]} - $:.push(*r[4]).uniq! - return true - when /^p (.+?)$/ - del_jobs_status - print $1.unpack("m")[0] - jobs_status if @options[:job_status] == :replace - when /^after (.+?)$/ - @warnings << Marshal.load($1.unpack("m")[0]) - when /^bye (.+?)$/ - after_worker_down worker, Marshal.load($1.unpack("m")[0]) - when /^bye$/, nil - if shutting_down || worker.quit_called - after_worker_quit worker - else - after_worker_down worker - end - else - print "unknown command: #{cmd.dump}\n" - end - return false - end - - def _run_parallel suites, type, result - if @options[:parallel] < 1 - warn "Error: parameter of -j option should be greater than 0." - return - end - - # Require needed things for parallel running - require 'thread' - require 'timeout' - @tasks = @files.dup # Array of filenames. - @need_quit = false - @dead_workers = [] # Array of dead workers. - @warnings = [] - @total_tests = @tasks.size.to_s(10) - rep = [] # FIXME: more good naming - - @workers = [] # Array of workers. - @workers_hash = {} # out-IO => worker - @ios = [] # Array of worker IOs - begin - @options[:parallel].times {launch_worker} - - while _io = IO.select(@ios)[0] - break if _io.any? do |io| - @need_quit or - (deal(io, type, result, rep).nil? and - !@workers.any? {|x| [:running, :prepare].include? x.status}) - end - end - rescue Interrupt => ex - @interrupt = ex - return result - ensure - if @interrupt - @ios.select!{|x| @workers_hash[x].status == :running } - while !@ios.empty? && (__io = IO.select(@ios,[],[],10)) - __io[0].reject! {|io| deal(io, type, result, rep, true)} - end - end - - quit_workers - - unless @interrupt || !@options[:retry] || @need_quit - @options[:parallel] = false - suites, rep = rep.partition {|r| r[:testcase] && r[:file] && r[:report].any? {|e| !e[2].is_a?(MiniTest::Skip)}} - suites.map {|r| r[:file]}.uniq.each {|file| require file} - suites.map! {|r| eval("::"+r[:testcase])} - del_status_line or puts - unless suites.empty? - puts "Retrying..." - _run_suites(suites, type) - end - end - unless @options[:retry] - del_status_line or puts - end - unless rep.empty? - rep.each do |r| - r[:report].each do |f| - puke(*f) if f - end - end - if @options[:retry] - @errors += rep.map{|x| x[:result][0] }.inject(:+) - @failures += rep.map{|x| x[:result][1] }.inject(:+) - @skips += rep.map{|x| x[:result][2] }.inject(:+) - end - end - unless @warnings.empty? - warn "" - @warnings.uniq! {|w| w[1].message} - @warnings.each do |w| - warn "#{w[0]}: #{w[1].message} (#{w[1].class})" - end - warn "" - end - end - end - - def _run_suites suites, type - _prepare_run(suites, type) - @interrupt = nil - result = [] - GC.start - if @options[:parallel] - _run_parallel suites, type, result - else - suites.each {|suite| - begin - result << _run_suite(suite, type) - rescue Interrupt => e - @interrupt = e - break - end - } - end - result - end - end - - module Skipping # :nodoc: all - private - def setup_options(opts, options) - super - - opts.separator "skipping options:" - - options[:hide_skip] = true - - opts.on '-q', '--hide-skip', 'Hide skipped tests' do - options[:hide_skip] = true - end - - opts.on '--show-skip', 'Show skipped tests' do - options[:hide_skip] = false - end - end - - private - def _run_suites(suites, type) - result = super - report.reject!{|r| r.start_with? "Skipped:" } if @options[:hide_skip] - report.sort_by!{|r| r.start_with?("Skipped:") ? 0 : \ - (r.start_with?("Failure:") ? 1 : 2) } - result - end - end - - module StatusLine # :nodoc: all - def terminal_width - unless @terminal_width ||= nil - begin - require 'io/console' - width = $stdout.winsize[1] - rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL - width = ENV["COLUMNS"].to_i.nonzero? || 80 - end - width -= 1 if /mswin|mingw/ =~ RUBY_PLATFORM - @terminal_width = width - end - @terminal_width - end - - def del_status_line(flush = true) - @status_line_size ||= 0 - unless @options[:job_status] == :replace - $stdout.puts - return - end - print "\r"+" "*@status_line_size+"\r" - $stdout.flush if flush - @status_line_size = 0 - end - - def add_status(line, flush: true) - unless @options[:job_status] == :replace - print(line) - return - end - @status_line_size ||= 0 - line = line[0...(terminal_width-@status_line_size)] - print line - $stdout.flush if flush - @status_line_size += line.size - end - - def jobs_status - return unless @options[:job_status] - puts "" unless @options[:verbose] or @options[:job_status] == :replace - status_line = @workers.map(&:to_s).join(" ") - update_status(status_line) or (puts; nil) - end - - def del_jobs_status - return unless @options[:job_status] == :replace && @status_line_size.nonzero? - del_status_line - end - - def output - (@output ||= nil) || super - end - - def _prepare_run(suites, type) - options[:job_status] ||= :replace if @tty && !@verbose - case options[:color] - when :always - color = true - when :auto, nil - color = (@tty || @options[:job_status] == :replace) && /dumb/ !~ ENV["TERM"] - else - color = false - end - if color - # dircolors-like style - colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {} - begin - File.read(File.join(__dir__, "../../colors")).scan(/(\w+)=([^:\n]*)/) do |n, c| - colors[n] ||= c - end - rescue - end - @passed_color = "\e[;#{colors["pass"] || "32"}m" - @failed_color = "\e[;#{colors["fail"] || "31"}m" - @skipped_color = "\e[;#{colors["skip"] || "33"}m" - @reset_color = "\e[m" - else - @passed_color = @failed_color = @skipped_color = @reset_color = "" - end - if color or @options[:job_status] == :replace - @verbose = !options[:parallel] - @output = Output.new(self) - end - if /\A\/(.*)\/\z/ =~ (filter = options[:filter]) - filter = Regexp.new($1) - end - type = "#{type}_methods" - total = if filter - suites.inject(0) {|n, suite| n + suite.send(type).grep(filter).size} - else - suites.inject(0) {|n, suite| n + suite.send(type).size} - end - @test_count = 0 - @total_tests = total.to_s(10) - end - - def new_test(s) - @test_count += 1 - update_status(s) - end - - def update_status(s) - count = @test_count.to_s(10).rjust(@total_tests.size) - del_status_line(false) if @options[:job_status] == :replace - print(@passed_color) - add_status("[#{count}/#{@total_tests}]", flush: false) - print(@reset_color) - add_status(" #{s}") - end - - def _print(s); $stdout.print(s); end - def succeed; del_status_line; end - - def failed(s) - sep = "\n" - @report_count ||= 0 - report.each do |msg| - if msg.start_with? "Skipped:" - if @options[:hide_skip] - del_status_line - next - end - color = @skipped_color - else - color = @failed_color - end - msg = msg.split(/$/, 2) - $stdout.printf("%s%s%3d) %s%s%s\n", - sep, color, @report_count += 1, - msg[0], @reset_color, msg[1]) - sep = nil - end - report.clear - end - - def initialize - super - @tty = $stdout.tty? - end - - def run(*args) - result = super - puts "\nruby -v: #{RUBY_DESCRIPTION}" - result - end - - private - def setup_options(opts, options) - super - - opts.separator "status line options:" - - options[:job_status] = nil - - opts.on '--jobs-status [TYPE]', [:normal, :replace], - "Show status of jobs every file; Disabled when --jobs isn't specified." do |type| - options[:job_status] = type || :normal - end - - opts.on '--color[=WHEN]', - [:always, :never, :auto], - "colorize the output. WHEN defaults to 'always'", "or can be 'never' or 'auto'." do |c| - options[:color] = c || :always - end - - opts.on '--tty[=WHEN]', - [:yes, :no], - "force to output tty control. WHEN defaults to 'yes'", "or can be 'no'." do |c| - @tty = c != :no - end - end - - class Output < Struct.new(:runner) # :nodoc: all - def puts(*a) $stdout.puts(*a) unless a.empty? end - def respond_to_missing?(*a) $stdout.respond_to?(*a) end - def method_missing(*a, &b) $stdout.__send__(*a, &b) end - - def print(s) - case s - when /\A(.*\#.*) = \z/ - runner.new_test($1) - when /\A(.* s) = \z/ - runner.add_status(" = "+$1.chomp) - when /\A\.+\z/ - runner.succeed - when /\A[EFS]\z/ - runner.failed(s) - else - $stdout.print(s) - end - end - end - end - - module LoadPathOption # :nodoc: all - def non_options(files, options) - begin - require "rbconfig" - rescue LoadError - warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument" - options[:parallel] = nil - else - options[:ruby] ||= [RbConfig.ruby] - end - - super - end - - def setup_options(parser, options) - super - parser.separator "load path options:" - parser.on '-Idirectory', 'Add library load path' do |dirs| - dirs.split(':').each { |d| $LOAD_PATH.unshift d } - end - end - end - - module GlobOption # :nodoc: all - @@testfile_prefix = "test" - - def setup_options(parser, options) - super - parser.separator "globbing options:" - parser.on '-b', '--basedir=DIR', 'Base directory of test suites.' do |dir| - options[:base_directory] = dir - end - parser.on '-x', '--exclude REGEXP', 'Exclude test files on pattern.' do |pattern| - (options[:reject] ||= []) << pattern - end - end - - def non_options(files, options) - paths = [options.delete(:base_directory), nil].uniq - if reject = options.delete(:reject) - reject_pat = Regexp.union(reject.map {|r| %r"#{r}"}) - end - files.map! {|f| - f = f.tr(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR - ((paths if /\A\.\.?(?:\z|\/)/ !~ f) || [nil]).any? do |prefix| - if prefix - path = f.empty? ? prefix : "#{prefix}/#{f}" - else - next if f.empty? - path = f - end - if !(match = Dir["#{path}/**/#{@@testfile_prefix}_*.rb"]).empty? - if reject - match.reject! {|n| - n[(prefix.length+1)..-1] if prefix - reject_pat =~ n - } - end - break match - elsif !reject or reject_pat !~ f and File.exist? path - break path - end - end or - raise ArgumentError, "file not found: #{f}" - } - files.flatten! - super(files, options) - end - end - - module GCStressOption # :nodoc: all - def setup_options(parser, options) - super - parser.separator "GC options:" - parser.on '--[no-]gc-stress', 'Set GC.stress as true' do |flag| - options[:gc_stress] = flag - end - end - - def non_options(files, options) - if options.delete(:gc_stress) - MiniTest::Unit::TestCase.class_eval do - oldrun = instance_method(:run) - define_method(:run) do |runner| - begin - gc_stress, GC.stress = GC.stress, true - oldrun.bind(self).call(runner) - ensure - GC.stress = gc_stress - end - end - end - end - super - end - end - - module RequireFiles # :nodoc: all - def non_options(files, options) - return false if !super - errors = {} - result = false - files.each {|f| - d = File.dirname(path = File.realpath(f)) - unless $:.include? d - $: << d - end - begin - require path unless options[:parallel] - result = true - rescue LoadError - next if errors[$!.message] - errors[$!.message] = true - puts "#{f}: #{$!}" - end - } - result - end - end - - module ExcludesOption # :nodoc: all - class ExcludedMethods < Struct.new(:excludes) - def exclude(name, reason) - excludes[name] = reason - end - - def exclude_from(klass) - excludes = self.excludes - pattern = excludes.keys.grep(Regexp).tap {|k| - break (Regexp.new(k.join('|')) unless k.empty?) - } - klass.class_eval do - public_instance_methods(false).each do |method| - if excludes[method] or (pattern and pattern =~ method) - remove_method(method) - end - end - public_instance_methods(true).each do |method| - if excludes[method] or (pattern and pattern =~ method) - undef_method(method) - end - end - end - end - - def self.load(dirs, name) - return unless dirs and name - instance = nil - dirs.each do |dir| - path = File.join(dir, name.gsub(/::/, '/') + ".rb") - begin - src = File.read(path) - rescue Errno::ENOENT - nil - else - instance ||= new({}) - instance.instance_eval(src) - end - end - instance - end - end - - def setup_options(parser, options) - super - if excludes = ENV["EXCLUDES"] - excludes = excludes.split(File::PATH_SEPARATOR) - end - options[:excludes] = excludes || [] - parser.on '-X', '--excludes-dir DIRECTORY', "Directory name of exclude files" do |d| - options[:excludes].concat d.split(File::PATH_SEPARATOR) - end - end - - def _run_suite(suite, type) - if ex = ExcludedMethods.load(@options[:excludes], suite.name) - ex.exclude_from(suite) - end - super - end - end - - class Runner < MiniTest::Unit # :nodoc: all - include Test::Unit::Options - include Test::Unit::StatusLine - include Test::Unit::Parallel - include Test::Unit::Skipping - include Test::Unit::GlobOption - include Test::Unit::LoadPathOption - include Test::Unit::GCStressOption - include Test::Unit::ExcludesOption - include Test::Unit::RunCount - - class << self; undef autorun; end - - @@stop_auto_run = false - def self.autorun - at_exit { - Test::Unit::RunCount.run_once { - exit(Test::Unit::Runner.new.run(ARGV) || true) - } unless @@stop_auto_run - } unless @@installed_at_exit - @@installed_at_exit = true - end - - alias mini_run_suite _run_suite - - # Overriding of MiniTest::Unit#puke - def puke klass, meth, e - # TODO: - # this overriding is for minitest feature that skip messages are - # hidden when not verbose (-v), note this is temporally. - n = report.size - rep = super - if MiniTest::Skip === e and /no message given\z/ =~ e.message - report.slice!(n..-1) - rep = "." - end - rep - end - end - - class AutoRunner # :nodoc: all - class Runner < Test::Unit::Runner - include Test::Unit::RequireFiles - end - - attr_accessor :to_run, :options - - def initialize(force_standalone = false, default_dir = nil, argv = ARGV) - @force_standalone = force_standalone - @runner = Runner.new do |files, options| - options[:base_directory] ||= default_dir - files << default_dir if files.empty? and default_dir - @to_run = files - yield self if block_given? - files - end - Runner.runner = @runner - @options = @runner.option_parser - if @force_standalone - @options.banner.sub!(/\[options\]/, '\& tests...') - end - @argv = argv - end - - def process_args(*args) - @runner.process_args(*args) - !@to_run.empty? - end - - def run - if @force_standalone and not process_args(@argv) - abort @options.banner - end - @runner.run(@argv) || true - end - - def self.run(*args) - new(*args).run - end - end - - class ProxyError < StandardError # :nodoc: all - def initialize(ex) - @message = ex.message - @backtrace = ex.backtrace - end - - attr_accessor :message, :backtrace - end - end -end - -module MiniTest # :nodoc: all - class Unit - end -end - -class MiniTest::Unit::TestCase # :nodoc: all - test_order = self.test_order - class << self - attr_writer :test_order - undef test_order - end - def self.test_order - defined?(@test_order) ? @test_order : superclass.test_order - end - self.test_order = test_order - undef run_test - RUN_TEST_TRACE = "#{__FILE__}:#{__LINE__+3}:in `run_test'".freeze - def run_test(name) - progname, $0 = $0, "#{$0}: #{self.class}##{name}" - self.__send__(name) - ensure - $@.delete(RUN_TEST_TRACE) if $@ - $0 = progname - end -end - -Test::Unit::Runner.autorun diff --git a/test/lib/test/unit/assertions.rb b/test/lib/test/unit/assertions.rb deleted file mode 100644 index ee6a758f..00000000 --- a/test/lib/test/unit/assertions.rb +++ /dev/null @@ -1,940 +0,0 @@ -# frozen_string_literal: true -require 'minitest/unit' -require 'pp' - -module Test - module Unit - module Assertions - include MiniTest::Assertions - - def mu_pp(obj) #:nodoc: - obj.pretty_inspect.chomp - end - - MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: - - # :call-seq: - # assert(test, [failure_message]) - # - #Tests if +test+ is true. - # - #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used - #as the failure message. Otherwise, the result of calling +msg+ will be - #used as the message if the assertion fails. - # - #If no +msg+ is given, a default message will be used. - # - # assert(false, "This was expected to be true") - def assert(test, *msgs) - case msg = msgs.first - when String, Proc - when nil - msgs.shift - else - bt = caller.reject { |s| s.start_with?(MINI_DIR) } - raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt - end unless msgs.empty? - super - end - - # :call-seq: - # assert_block( failure_message = nil ) - # - #Tests the result of the given block. If the block does not return true, - #the assertion will fail. The optional +failure_message+ argument is the same as in - #Assertions#assert. - # - # assert_block do - # [1, 2, 3].any? { |num| num < 1 } - # end - def assert_block(*msgs) - assert yield, *msgs - end - - # :call-seq: - # assert_raise( *args, &block ) - # - #Tests if the given block raises an exception. Acceptable exception - #types may be given as optional arguments. If the last argument is a - #String, it will be used as the error message. - # - # assert_raise do #Fails, no Exceptions are raised - # end - # - # assert_raise NameError do - # puts x #Raises NameError, so assertion succeeds - # end - def assert_raise(*exp, &b) - case exp.last - when String, Proc - msg = exp.pop - end - - begin - yield - rescue MiniTest::Skip => e - return e if exp.include? MiniTest::Skip - raise e - rescue Exception => e - expected = exp.any? { |ex| - if ex.instance_of? Module then - e.kind_of? ex - else - e.instance_of? ex - end - } - - assert expected, proc { - exception_details(e, message(msg) {"#{mu_pp(exp)} exception expected, not"}.call) - } - - return e - end - - exp = exp.first if exp.size == 1 - - flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) - end - - def assert_raises(*exp, &b) - raise NoMethodError, "use assert_raise", caller - end - - # :call-seq: - # assert_raise_with_message(exception, expected, msg = nil, &block) - # - #Tests if the given block raises an exception with the expected - #message. - # - # assert_raise_with_message(RuntimeError, "foo") do - # nil #Fails, no Exceptions are raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise ArgumentError, "foo" #Fails, different Exception is raised - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "bar" #Fails, RuntimeError is raised but the message differs - # end - # - # assert_raise_with_message(RuntimeError, "foo") do - # raise "foo" #Raises RuntimeError with the message, so assertion succeeds - # end - def assert_raise_with_message(exception, expected, msg = nil, &block) - case expected - when String - assert = :assert_equal - when Regexp - assert = :assert_match - else - raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" - end - - ex = m = nil - EnvUtil.with_default_internal(expected.encoding) do - ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do - yield - end - m = ex.message - end - msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} - - if assert == :assert_equal - assert_equal(expected, m, msg) - else - msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } - assert expected =~ m, msg - block.binding.eval("proc{|_|$~=_}").call($~) - end - ex - end - - # :call-seq: - # assert_nothing_raised( *args, &block ) - # - #If any exceptions are given as arguments, the assertion will - #fail if one of those exceptions are raised. Otherwise, the test fails - #if any exceptions are raised. - # - #The final argument may be a failure message. - # - # assert_nothing_raised RuntimeError do - # raise Exception #Assertion passes, Exception is not a RuntimeError - # end - # - # assert_nothing_raised do - # raise Exception #Assertion fails - # end - def assert_nothing_raised(*args) - self._assertions += 1 - if Module === args.last - msg = nil - else - msg = args.pop - end - begin - line = __LINE__; yield - rescue MiniTest::Skip - raise - rescue Exception => e - bt = e.backtrace - as = e.instance_of?(MiniTest::Assertion) - if as - ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o - bt.reject! {|ln| ans =~ ln} - end - if ((args.empty? && !as) || - args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) - msg = message(msg) { "Exception raised:\n<#{mu_pp(e)}>" } - raise MiniTest::Assertion, msg.call, bt - else - raise - end - end - end - - # :call-seq: - # assert_nothing_thrown( failure_message = nil, &block ) - # - #Fails if the given block uses a call to Kernel#throw, and - #returns the result of the block otherwise. - # - #An optional failure message may be provided as the final argument. - # - # assert_nothing_thrown "Something was thrown!" do - # throw :problem? - # end - def assert_nothing_thrown(msg=nil) - begin - ret = yield - rescue ArgumentError => error - raise error if /\Auncaught throw (.+)\z/m !~ error.message - msg = message(msg) { "<#{$1}> was thrown when nothing was expected" } - flunk(msg) - end - assert(true, "Expected nothing to be thrown") - ret - end - - # :call-seq: - # assert_throw( tag, failure_message = nil, &block ) - # - #Fails unless the given block throws +tag+, returns the caught - #value otherwise. - # - #An optional failure message may be provided as the final argument. - # - # tag = Object.new - # assert_throw(tag, "#{tag} was not thrown!") do - # throw tag - # end - def assert_throw(tag, msg = nil) - ret = catch(tag) do - begin - yield(tag) - rescue UncaughtThrowError => e - thrown = e.tag - end - msg = message(msg) { - "Expected #{mu_pp(tag)} to have been thrown"\ - "#{%Q[, not #{thrown}] if thrown}" - } - assert(false, msg) - end - assert(true) - ret - end - - # :call-seq: - # assert_equal( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is equal to +actual+. - # - #An optional failure message may be provided as the final argument. - def assert_equal(exp, act, msg = nil) - msg = message(msg) { - exp_str = mu_pp(exp) - act_str = mu_pp(act) - exp_comment = '' - act_comment = '' - if exp_str == act_str - if (exp.is_a?(String) && act.is_a?(String)) || - (exp.is_a?(Regexp) && act.is_a?(Regexp)) - exp_comment = " (#{exp.encoding})" - act_comment = " (#{act.encoding})" - elsif exp.is_a?(Float) && act.is_a?(Float) - exp_str = "%\#.#{Float::DIG+2}g" % exp - act_str = "%\#.#{Float::DIG+2}g" % act - elsif exp.is_a?(Time) && act.is_a?(Time) - if exp.subsec * 1000_000_000 == exp.nsec - exp_comment = " (#{exp.nsec}[ns])" - else - exp_comment = " (subsec=#{exp.subsec})" - end - if act.subsec * 1000_000_000 == act.nsec - act_comment = " (#{act.nsec}[ns])" - else - act_comment = " (subsec=#{act.subsec})" - end - elsif exp.class != act.class - # a subclass of Range, for example. - exp_comment = " (#{exp.class})" - act_comment = " (#{act.class})" - end - elsif !Encoding.compatible?(exp_str, act_str) - if exp.is_a?(String) && act.is_a?(String) - exp_str = exp.dump - act_str = act.dump - exp_comment = " (#{exp.encoding})" - act_comment = " (#{act.encoding})" - else - exp_str = exp_str.dump - act_str = act_str.dump - end - end - "<#{exp_str}>#{exp_comment} expected but was\n<#{act_str}>#{act_comment}" - } - assert(exp == act, msg) - end - - # :call-seq: - # assert_not_nil( expression, failure_message = nil ) - # - #Tests if +expression+ is not nil. - # - #An optional failure message may be provided as the final argument. - def assert_not_nil(exp, msg=nil) - msg = message(msg) { "<#{mu_pp(exp)}> expected to not be nil" } - assert(!exp.nil?, msg) - end - - # :call-seq: - # assert_not_equal( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is not equal to +actual+. - # - #An optional failure message may be provided as the final argument. - def assert_not_equal(exp, act, msg=nil) - msg = message(msg) { "<#{mu_pp(exp)}> expected to be != to\n<#{mu_pp(act)}>" } - assert(exp != act, msg) - end - - # :call-seq: - # assert_no_match( regexp, string, failure_message = nil ) - # - #Tests if the given Regexp does not match a given String. - # - #An optional failure message may be provided as the final argument. - def assert_no_match(regexp, string, msg=nil) - assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.") - self._assertions -= 1 - msg = message(msg) { "<#{mu_pp(regexp)}> expected to not match\n<#{mu_pp(string)}>" } - assert(regexp !~ string, msg) - end - - # :call-seq: - # assert_not_same( expected, actual, failure_message = nil ) - # - #Tests if +expected+ is not the same object as +actual+. - #This test uses Object#equal? to test equality. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_same("x", "x") #Succeeds - def assert_not_same(expected, actual, message="") - msg = message(msg) { build_message(message, < -with id expected to not be equal\\? to - -with id . -EOT - assert(!actual.equal?(expected), msg) - end - - # :call-seq: - # assert_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object responds to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_respond_to("hello", :reverse) #Succeeds - # assert_respond_to("hello", :does_not_exist) #Fails - def assert_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" - } - return assert obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return if obj.respond_to?(meth) - end - super(obj, meth, msg) - end - - # :call-seq: - # assert_not_respond_to( object, method, failure_message = nil ) - # - #Tests if the given Object does not respond to +method+. - # - #An optional failure message may be provided as the final argument. - # - # assert_not_respond_to("hello", :reverse) #Fails - # assert_not_respond_to("hello", :does_not_exist) #Succeeds - def assert_not_respond_to(obj, (meth, *priv), msg = nil) - unless priv.empty? - msg = message(msg) { - "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" - } - return assert !obj.respond_to?(meth, *priv), msg - end - #get rid of overcounting - if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) - return unless obj.respond_to?(meth) - end - refute_respond_to(obj, meth, msg) - end - - # :call-seq: - # assert_send( +send_array+, failure_message = nil ) - # - # Passes if the method send returns a true value. - # - # +send_array+ is composed of: - # * A receiver - # * A method - # * Arguments to the method - # - # Example: - # assert_send(["Hello world", :include?, "Hello"]) # -> pass - # assert_send(["Hello world", :include?, "Goodbye"]) # -> fail - def assert_send send_ary, m = nil - recv, msg, *args = send_ary - m = message(m) { - if args.empty? - argsstr = "" - else - (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') - end - "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return true" - } - assert recv.__send__(msg, *args), m - end - - # :call-seq: - # assert_not_send( +send_array+, failure_message = nil ) - # - # Passes if the method send doesn't return a true value. - # - # +send_array+ is composed of: - # * A receiver - # * A method - # * Arguments to the method - # - # Example: - # assert_not_send([[1, 2], :member?, 1]) # -> fail - # assert_not_send([[1, 2], :member?, 4]) # -> pass - def assert_not_send send_ary, m = nil - recv, msg, *args = send_ary - m = message(m) { - if args.empty? - argsstr = "" - else - (argsstr = mu_pp(args)).sub!(/\A\[(.*)\]\z/m, '(\1)') - end - "Expected #{mu_pp(recv)}.#{msg}#{argsstr} to return false" - } - assert !recv.__send__(msg, *args), m - end - - ms = instance_methods(true).map {|sym| sym.to_s } - ms.grep(/\Arefute_/) do |m| - mname = ('assert_not_'.dup << m.to_s[/.*?_(.*)/, 1]) - alias_method(mname, m) unless ms.include? mname - end - alias assert_include assert_includes - alias assert_not_include assert_not_includes - - def assert_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - unless blk.call(*a, &b) - failed << (a.size > 1 ? a : a[0]) - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - def assert_not_all?(obj, m = nil, &blk) - failed = [] - obj.each do |*a, &b| - if blk.call(*a, &b) - failed << a.size > 1 ? a : a[0] - end - end - assert(failed.empty?, message(m) {failed.pretty_inspect}) - end - - # compatibility with test-unit - alias pend skip - - if defined?(RubyVM::InstructionSequence) - def syntax_check(code, fname, line) - code = code.dup.force_encoding(Encoding::UTF_8) - RubyVM::InstructionSequence.compile(code, fname, fname, line) - :ok - end - else - def syntax_check(code, fname, line) - code = code.b - code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { - "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" - } - code = code.force_encoding(Encoding::UTF_8) - catch {|tag| eval(code, binding, fname, line - 1)} - end - end - - def prepare_syntax_check(code, fname = caller_locations(2, 1)[0], mesg = fname.to_s, verbose: nil) - verbose, $VERBOSE = $VERBOSE, verbose - case - when Array === fname - fname, line = *fname - when defined?(fname.path) && defined?(fname.lineno) - fname, line = fname.path, fname.lineno - else - line = 1 - end - yield(code, fname, line, mesg) - ensure - $VERBOSE = verbose - end - - def assert_valid_syntax(code, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - assert_nothing_raised(SyntaxError, mesg) do - assert_equal(:ok, syntax_check(src, fname, line), mesg) - end - end - end - - def assert_syntax_error(code, error, *args) - prepare_syntax_check(code, *args) do |src, fname, line, mesg| - yield if defined?(yield) - e = assert_raise(SyntaxError, mesg) do - syntax_check(src, fname, line) - end - assert_match(error, e.message, mesg) - e - end - end - - def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) - assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) - if child_env - child_env = [child_env] - else - child_env = [] - end - out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) - assert !status.signaled?, FailDesc[status, message, out] - end - - FailDesc = proc do |status, message = "", out = ""| - pid = status.pid - now = Time.now - faildesc = proc do - if signo = status.termsig - signame = Signal.signame(signo) - sigdesc = "signal #{signo}" - end - log = EnvUtil.diagnostic_reports(signame, pid, now) - if signame - sigdesc = "SIG#{signame} (#{sigdesc})" - end - if status.coredump? - sigdesc = "#{sigdesc} (core dumped)" - end - full_message = ''.dup - message = message.call if Proc === message - if message and !message.empty? - full_message << message << "\n" - end - full_message << "pid #{pid}" - full_message << " exit #{status.exitstatus}" if status.exited? - full_message << " killed by #{sigdesc}" if sigdesc - if out and !out.empty? - full_message << "\n" << out.b.gsub(/^/, '| ') - full_message.sub!(/(? marshal_error - ignore_stderr = nil - end - if res - if bt = res.backtrace - bt.each do |l| - l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} - end - bt.concat(caller) - else - res.set_backtrace(caller) - end - raise res unless SystemExit === res - end - - # really is it succeed? - unless ignore_stderr - # the body of assert_separately must not output anything to detect error - assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) - end - assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) - raise marshal_error if marshal_error - end - - def assert_warning(pat, msg = nil) - stderr = EnvUtil.verbose_warning { - EnvUtil.with_default_internal(pat.encoding) { - yield - } - } - msg = message(msg) {diff pat, stderr} - assert(pat === stderr, msg) - end - - def assert_warn(*args) - assert_warning(*args) {$VERBOSE = false; yield} - end - - def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) - require_relative '../../memory_status' - raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) - - token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" - token_dump = token.dump - token_re = Regexp.quote(token) - envs = args.shift if Array === args and Hash === args.first - args = [ - "--disable=gems", - "-r", File.expand_path("../../../memory_status", __FILE__), - *args, - "-v", "-", - ] - if defined? Memory::NO_MEMORY_LEAK_ENVS then - envs ||= {} - newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break } - envs = newenvs if newenvs - end - args.unshift(envs) if envs - cmd = [ - 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', - prepare, - 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', - '$initial_size = $initial_status.size', - code, - 'GC.start', - ].join("\n") - _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) - before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) - after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) - assert(status.success?, FailDesc[status, message, err]) - ([:size, (rss && :rss)] & after.members).each do |n| - b = before[n] - a = after[n] - next unless a > 0 and b > 0 - assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) - end - rescue LoadError - skip - end - - def assert_cpu_usage_low(msg = nil, pct: 0.01) - require 'benchmark' - - tms = Benchmark.measure(msg || '') { yield } - max = pct * tms.real - if tms.real < 0.1 # TIME_QUANTUM_USEC in thread_pthread.c - warn "test #{msg || 'assert_cpu_usage_low'} too short to be accurate" - end - - # kernel resolution can limit the minimum time we can measure - # [ruby-core:81540] - min_hz = windows? ? 67 : 100 - min_measurable = 1.0 / min_hz - min_measurable *= 1.10 # add a little (10%) to account for misc. overheads - if max < min_measurable - max = min_measurable - end - - assert_operator tms.total, :<=, max, msg - end - - def assert_is_minus_zero(f) - assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0") - end - - def assert_file - AssertFile - end - - # pattern_list is an array which contains regexp and :*. - # :* means any sequence. - # - # pattern_list is anchored. - # Use [:*, regexp, :*] for non-anchored match. - def assert_pattern_list(pattern_list, actual, message=nil) - rest = actual - anchored = true - pattern_list.each_with_index {|pattern, i| - if pattern == :* - anchored = false - else - if anchored - match = /\A#{pattern}/.match(rest) - else - match = pattern.match(rest) - end - unless match - msg = message(msg) { - expect_msg = "Expected #{mu_pp pattern}\n" - if /\n[^\n]/ =~ rest - actual_mesg = "to match\n" - rest.scan(/.*\n+/) { - actual_mesg << ' ' << $&.inspect << "+\n" - } - actual_mesg.sub!(/\+\n\z/, '') - else - actual_mesg = "to match #{mu_pp rest}" - end - actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" - expect_msg + actual_mesg - } - assert false, msg - end - rest = match.post_match - anchored = true - end - } - if anchored - assert_equal("", rest) - end - end - - # threads should respond to shift method. - # Array can be used. - def assert_join_threads(threads, message = nil) - errs = [] - values = [] - while th = threads.shift - begin - values << th.value - rescue Exception - errs << [th, $!] - end - end - if !errs.empty? - msg = "exceptions on #{errs.length} threads:\n" + - errs.map {|t, err| - "#{t.inspect}:\n" + - err.backtrace.map.with_index {|line, i| - if i == 0 - "#{line}: #{err.message} (#{err.class})" - else - "\tfrom #{line}" - end - }.join("\n") - }.join("\n---\n") - if message - msg = "#{message}\n#{msg}" - end - raise MiniTest::Assertion, msg - end - values - end - - class << (AssertFile = Struct.new(:failure_message).new) - include Assertions - def assert_file_predicate(predicate, *args) - if /\Anot_/ =~ predicate - predicate = $' - neg = " not" - end - result = File.__send__(predicate, *args) - result = !result if neg - mesg = "Expected file ".dup << args.shift.inspect - mesg << "#{neg} to be #{predicate}" - mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? - mesg << " #{failure_message}" if failure_message - assert(result, mesg) - end - alias method_missing assert_file_predicate - - def for(message) - clone.tap {|a| a.failure_message = message} - end - end - - class AllFailures - attr_reader :failures - - def initialize - @count = 0 - @failures = {} - end - - def for(key) - @count += 1 - yield - rescue Exception => e - @failures[key] = [@count, e] - end - - def foreach(*keys) - keys.each do |key| - @count += 1 - begin - yield key - rescue Exception => e - @failures[key] = [@count, e] - end - end - end - - def message - i = 0 - total = @count.to_s - fmt = "%#{total.size}d" - @failures.map {|k, (n, v)| - "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.message.b.gsub(/^/, ' | ')}" - }.join("\n") - end - - def pass? - @failures.empty? - end - end - - def assert_all_assertions(msg = nil) - all = AllFailures.new - yield all - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions assert_all_assertions - - def assert_all_assertions_foreach(msg = nil, *keys, &block) - all = AllFailures.new - all.foreach(*keys, &block) - ensure - assert(all.pass?, message(msg) {all.message.chomp(".")}) - end - alias all_assertions_foreach assert_all_assertions_foreach - - def build_message(head, template=nil, *arguments) #:nodoc: - template &&= template.chomp - template.gsub(/\G((?:[^\\]|\\.)*?)(\\)?\?/) { $1 + ($2 ? "?" : mu_pp(arguments.shift)) } - end - - def message(msg = nil, *args, &default) # :nodoc: - if Proc === msg - super(nil, *args) do - ary = [msg.call, (default.call if default)].compact.reject(&:empty?) - if 1 < ary.length - ary[0...-1] = ary[0...-1].map {|str| str.sub(/(? e - begin - trace = e.backtrace || ['unknown method'] - err = ["#{trace.shift}: #{e.message} (#{e.class})"] + trace.map{|t| t.prepend("\t") } - - _report "bye", Marshal.dump(err.join("\n")) - rescue Errno::EPIPE;end - exit - ensure - @stdin.close if @stdin - @stdout.close if @stdout - end - end - - def _report(res, *args) # :nodoc: - res = "#{res} #{args.pack("m0")}" unless args.empty? - @stdout.puts(res) - end - - def puke(klass, meth, e) # :nodoc: - if e.is_a?(MiniTest::Skip) - new_e = MiniTest::Skip.new(e.message) - new_e.set_backtrace(e.backtrace) - e = new_e - end - @partial_report << [klass.name, meth, e.is_a?(MiniTest::Assertion) ? e : ProxyError.new(e)] - super - end - end - end -end - -if $0 == __FILE__ - module Test - module Unit - class TestCase < MiniTest::Unit::TestCase # :nodoc: all - undef on_parallel_worker? - def on_parallel_worker? - true - end - end - end - end - require 'rubygems' - module Gem # :nodoc: - end - class Gem::TestCase < MiniTest::Unit::TestCase # :nodoc: - @@project_dir = File.expand_path('../../../../..', __FILE__) - end - - Test::Unit::Worker.new.run(ARGV) -end diff --git a/test/lib/test/unit/testcase.rb b/test/lib/test/unit/testcase.rb deleted file mode 100644 index 10348b5c..00000000 --- a/test/lib/test/unit/testcase.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: false -require 'test/unit/assertions' - -module Test - module Unit - # remove silly TestCase class - remove_const(:TestCase) if defined?(self::TestCase) - - class TestCase < MiniTest::Unit::TestCase # :nodoc: all - include Assertions - - def on_parallel_worker? - false - end - - def run runner - @options = runner.options - super runner - end - - def self.test_order - :sorted - end - - def self.method_added(name) - super - return unless name.to_s.start_with?("test_") - @test_methods ||= {} - if @test_methods[name] - warn "test/unit warning: method #{ self }##{ name } is redefined" - end - @test_methods[name] = true - end - end - end -end From 4e7990234487bdc644d6470e828ddfc3621beb08 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:29:42 +0900 Subject: [PATCH 096/144] Use Gemfile instead of Gem::Specification#add_development_dependency. --- Gemfile | 8 +++++++- bigdecimal.gemspec | 7 ------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index ed7382e0..6509f203 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,10 @@ source 'https://rubygems.org' -# Specify your gem's dependencies in bigdecimal.gemspec gemspec + +gem "benchmark_driver" +gem "fiddle" +gem "rake", ">= 12.3.3" +gem "rake-compiler", ">= 0.9" +gem "minitest", "< 5.0.0" +gem "irb" diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 2e017eee..36ff65a9 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -37,11 +37,4 @@ Gem::Specification.new do |s| ] s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") - - s.add_development_dependency "benchmark_driver" - s.add_development_dependency "fiddle" - s.add_development_dependency "rake", ">= 12.3.3" - s.add_development_dependency "rake-compiler", ">= 0.9" - s.add_development_dependency "minitest", "< 5.0.0" - s.add_development_dependency "irb" end From 0e5b65b05adc00b298dcf1982e3bf0ae25b54382 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:30:11 +0900 Subject: [PATCH 097/144] Added test-unit --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 6509f203..06413ee9 100644 --- a/Gemfile +++ b/Gemfile @@ -8,3 +8,4 @@ gem "rake", ">= 12.3.3" gem "rake-compiler", ">= 0.9" gem "minitest", "< 5.0.0" gem "irb" +gem "test-unit" From 7877eaad69b90c3e50dc36789841cd6ef3cf7361 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 2 Mar 2021 21:54:16 +0900 Subject: [PATCH 098/144] Port the minimum test libraries from ruby/ruby --- Rakefile | 8 + test/lib/core_assertions.rb | 763 ++++++++++++++++++++++++++++++++++++ test/lib/envutil.rb | 365 +++++++++++++++++ test/lib/find_executable.rb | 22 ++ test/lib/helper.rb | 4 + 5 files changed, 1162 insertions(+) create mode 100644 test/lib/core_assertions.rb create mode 100644 test/lib/envutil.rb create mode 100644 test/lib/find_executable.rb create mode 100644 test/lib/helper.rb diff --git a/Rakefile b/Rakefile index f4a67197..2a2f4da9 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,7 @@ Rake::ExtensionTask.new('bigdecimal', spec) Rake::TestTask.new do |t| t.libs << 'test/lib' + t.ruby_opts << '-rhelper' t.test_files = FileList['test/bigdecimal/**/test_*.rb'] t.warning = true end @@ -41,3 +42,10 @@ end desc "Run all benchmarks" task benchmark: benchmark_tasks + +task :sync_tool do + require 'fileutils' + FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" + FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" +end diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb new file mode 100644 index 00000000..118c0d11 --- /dev/null +++ b/test/lib/core_assertions.rb @@ -0,0 +1,763 @@ +# frozen_string_literal: true + +module Test + module Unit + module Assertions + def _assertions= n # :nodoc: + @_assertions = n + end + + def _assertions # :nodoc: + @_assertions ||= 0 + end + + ## + # Returns a proc that will output +msg+ along with the default message. + + def message msg = nil, ending = nil, &default + proc { + msg = msg.call.chomp(".") if Proc === msg + custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty? + "#{custom_message}#{default.call}#{ending || "."}" + } + end + end + + module CoreAssertions + if defined?(MiniTest) + require_relative '../../envutil' + # for ruby core testing + include MiniTest::Assertions + + # Compatibility hack for assert_raise + Test::Unit::AssertionFailedError = MiniTest::Assertion + else + module MiniTest + class Assertion < Exception; end + class Skip < Assertion; end + end + + require 'pp' + require_relative 'envutil' + include Test::Unit::Assertions + end + + def mu_pp(obj) #:nodoc: + obj.pretty_inspect.chomp + end + + def assert_file + AssertFile + end + + FailDesc = proc do |status, message = "", out = ""| + now = Time.now + proc do + EnvUtil.failure_description(status, now, message, out) + end + end + + def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, + success: nil, **opt) + args = Array(args).dup + args.insert((Hash === args[0] ? 1 : 0), '--disable=gems') + stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, **opt) + desc = FailDesc[status, message, stderr] + if block_given? + raise "test_stdout ignored, use block only or without block" if test_stdout != [] + raise "test_stderr ignored, use block only or without block" if test_stderr != [] + yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp }, status) + else + all_assertions(desc) do |a| + [["stdout", test_stdout, stdout], ["stderr", test_stderr, stderr]].each do |key, exp, act| + a.for(key) do + if exp.is_a?(Regexp) + assert_match(exp, act) + elsif exp.all? {|e| String === e} + assert_equal(exp, act.lines.map {|l| l.chomp }) + else + assert_pattern_list(exp, act) + end + end + end + unless success.nil? + a.for("success?") do + if success + assert_predicate(status, :success?) + else + assert_not_predicate(status, :success?) + end + end + end + end + status + end + end + + if defined?(RubyVM::InstructionSequence) + def syntax_check(code, fname, line) + code = code.dup.force_encoding(Encoding::UTF_8) + RubyVM::InstructionSequence.compile(code, fname, fname, line) + :ok + ensure + raise if SyntaxError === $! + end + else + def syntax_check(code, fname, line) + code = code.b + code.sub!(/\A(?:\xef\xbb\xbf)?(\s*\#.*$)*(\n)?/n) { + "#$&#{"\n" if $1 && !$2}BEGIN{throw tag, :ok}\n" + } + code = code.force_encoding(Encoding::UTF_8) + catch {|tag| eval(code, binding, fname, line - 1)} + end + end + + def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) + # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + + require_relative '../../memory_status' + raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) + + token = "\e[7;1m#{$$.to_s}:#{Time.now.strftime('%s.%L')}:#{rand(0x10000).to_s(16)}:\e[m" + token_dump = token.dump + token_re = Regexp.quote(token) + envs = args.shift if Array === args and Hash === args.first + args = [ + "--disable=gems", + "-r", File.expand_path("../../../memory_status", __FILE__), + *args, + "-v", "-", + ] + if defined? Memory::NO_MEMORY_LEAK_ENVS then + envs ||= {} + newenvs = envs.merge(Memory::NO_MEMORY_LEAK_ENVS) { |_, _, _| break } + envs = newenvs if newenvs + end + args.unshift(envs) if envs + cmd = [ + 'END {STDERR.puts '"#{token_dump}"'"FINAL=#{Memory::Status.new}"}', + prepare, + 'STDERR.puts('"#{token_dump}"'"START=#{$initial_status = Memory::Status.new}")', + '$initial_size = $initial_status.size', + code, + 'GC.start', + ].join("\n") + _, err, status = EnvUtil.invoke_ruby(args, cmd, true, true, **opt) + before = err.sub!(/^#{token_re}START=(\{.*\})\n/, '') && Memory::Status.parse($1) + after = err.sub!(/^#{token_re}FINAL=(\{.*\})\n/, '') && Memory::Status.parse($1) + assert(status.success?, FailDesc[status, message, err]) + ([:size, (rss && :rss)] & after.members).each do |n| + b = before[n] + a = after[n] + next unless a > 0 and b > 0 + assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"}) + end + rescue LoadError + pend + end + + # :call-seq: + # assert_nothing_raised( *args, &block ) + # + #If any exceptions are given as arguments, the assertion will + #fail if one of those exceptions are raised. Otherwise, the test fails + #if any exceptions are raised. + # + #The final argument may be a failure message. + # + # assert_nothing_raised RuntimeError do + # raise Exception #Assertion passes, Exception is not a RuntimeError + # end + # + # assert_nothing_raised do + # raise Exception #Assertion fails + # end + def assert_nothing_raised(*args) + self._assertions += 1 + if Module === args.last + msg = nil + else + msg = args.pop + end + begin + line = __LINE__; yield + rescue MiniTest::Skip + raise + rescue Exception => e + bt = e.backtrace + as = e.instance_of?(MiniTest::Assertion) + if as + ans = /\A#{Regexp.quote(__FILE__)}:#{line}:in /o + bt.reject! {|ln| ans =~ ln} + end + if ((args.empty? && !as) || + args.any? {|a| a.instance_of?(Module) ? e.is_a?(a) : e.class == a }) + msg = message(msg) { + "Exception raised:\n<#{mu_pp(e)}>\n" + + "Backtrace:\n" + + e.backtrace.map{|frame| " #{frame}"}.join("\n") + } + raise MiniTest::Assertion, msg.call, bt + else + raise + end + end + end + + def prepare_syntax_check(code, fname = nil, mesg = nil, verbose: nil) + fname ||= caller_locations(2, 1)[0] + mesg ||= fname.to_s + verbose, $VERBOSE = $VERBOSE, verbose + case + when Array === fname + fname, line = *fname + when defined?(fname.path) && defined?(fname.lineno) + fname, line = fname.path, fname.lineno + else + line = 1 + end + yield(code, fname, line, message(mesg) { + if code.end_with?("\n") + "```\n#{code}```\n" + else + "```\n#{code}\n```\n""no-newline" + end + }) + ensure + $VERBOSE = verbose + end + + def assert_valid_syntax(code, *args, **opt) + prepare_syntax_check(code, *args, **opt) do |src, fname, line, mesg| + yield if defined?(yield) + assert_nothing_raised(SyntaxError, mesg) do + assert_equal(:ok, syntax_check(src, fname, line), mesg) + end + end + end + + def assert_normal_exit(testsrc, message = '', child_env: nil, **opt) + assert_valid_syntax(testsrc, caller_locations(1, 1)[0]) + if child_env + child_env = [child_env] + else + child_env = [] + end + out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, **opt) + assert !status.signaled?, FailDesc[status, message, out] + end + + def assert_ruby_status(args, test_stdin="", message=nil, **opt) + out, _, status = EnvUtil.invoke_ruby(args, test_stdin, true, :merge_to_stdout, **opt) + desc = FailDesc[status, message, out] + assert(!status.signaled?, desc) + message ||= "ruby exit status is not success:" + assert(status.success?, desc) + end + + ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") + + def separated_runner(out = nil) + out = out ? IO.new(out, 'w') : STDOUT + at_exit { + out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" + } + Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true) if defined?(Test::Unit::Runner) + end + + def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **opt) + unless file and line + loc, = caller_locations(1,1) + file ||= loc.path + line ||= loc.lineno + end + capture_stdout = true + unless /mswin|mingw/ =~ RUBY_PLATFORM + capture_stdout = false + opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) + res_p, res_c = IO.pipe + opt[res_c.fileno] = res_c.fileno + end + src = < marshal_error + ignore_stderr = nil + res = nil + end + if res and !(SystemExit === res) + if bt = res.backtrace + bt.each do |l| + l.sub!(/\A-:(\d+)/){"#{file}:#{line + $1.to_i}"} + end + bt.concat(caller) + else + res.set_backtrace(caller) + end + raise res + end + + # really is it succeed? + unless ignore_stderr + # the body of assert_separately must not output anything to detect error + assert(stderr.empty?, FailDesc[status, "assert_separately failed with error message", stderr]) + end + assert(status.success?, FailDesc[status, "assert_separately failed", stderr]) + raise marshal_error if marshal_error + end + + # Run Ractor-related test without influencing the main test suite + def assert_ractor(src, args: [], require: nil, require_relative: nil, file: nil, line: nil, ignore_stderr: nil, **opt) + return unless defined?(Ractor) + + require = "require #{require.inspect}" if require + if require_relative + dir = File.dirname(caller_locations[0,1][0].absolute_path) + full_path = File.expand_path(require_relative, dir) + require = "#{require}; require #{full_path.inspect}" + end + + assert_separately(args, file, line, <<~RUBY, ignore_stderr: ignore_stderr, **opt) + #{require} + previous_verbose = $VERBOSE + $VERBOSE = nil + Ractor.new {} # trigger initial warning + $VERBOSE = previous_verbose + #{src} + RUBY + end + + # :call-seq: + # assert_throw( tag, failure_message = nil, &block ) + # + #Fails unless the given block throws +tag+, returns the caught + #value otherwise. + # + #An optional failure message may be provided as the final argument. + # + # tag = Object.new + # assert_throw(tag, "#{tag} was not thrown!") do + # throw tag + # end + def assert_throw(tag, msg = nil) + ret = catch(tag) do + begin + yield(tag) + rescue UncaughtThrowError => e + thrown = e.tag + end + msg = message(msg) { + "Expected #{mu_pp(tag)} to have been thrown"\ + "#{%Q[, not #{thrown}] if thrown}" + } + assert(false, msg) + end + assert(true) + ret + end + + # :call-seq: + # assert_raise( *args, &block ) + # + #Tests if the given block raises an exception. Acceptable exception + #types may be given as optional arguments. If the last argument is a + #String, it will be used as the error message. + # + # assert_raise do #Fails, no Exceptions are raised + # end + # + # assert_raise NameError do + # puts x #Raises NameError, so assertion succeeds + # end + def assert_raise(*exp, &b) + case exp.last + when String, Proc + msg = exp.pop + end + + begin + yield + rescue MiniTest::Skip => e + return e if exp.include? MiniTest::Skip + raise e + rescue Exception => e + expected = exp.any? { |ex| + if ex.instance_of? Module then + e.kind_of? ex + else + e.instance_of? ex + end + } + + assert expected, proc { + flunk(message(msg) {"#{mu_pp(exp)} exception expected, not #{mu_pp(e)}"}) + } + + return e + ensure + unless e + exp = exp.first if exp.size == 1 + + flunk(message(msg) {"#{mu_pp(exp)} expected but nothing was raised"}) + end + end + end + + # :call-seq: + # assert_raise_with_message(exception, expected, msg = nil, &block) + # + #Tests if the given block raises an exception with the expected + #message. + # + # assert_raise_with_message(RuntimeError, "foo") do + # nil #Fails, no Exceptions are raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise ArgumentError, "foo" #Fails, different Exception is raised + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "bar" #Fails, RuntimeError is raised but the message differs + # end + # + # assert_raise_with_message(RuntimeError, "foo") do + # raise "foo" #Raises RuntimeError with the message, so assertion succeeds + # end + def assert_raise_with_message(exception, expected, msg = nil, &block) + case expected + when String + assert = :assert_equal + when Regexp + assert = :assert_match + else + raise TypeError, "Expected #{expected.inspect} to be a kind of String or Regexp, not #{expected.class}" + end + + ex = m = nil + EnvUtil.with_default_internal(expected.encoding) do + ex = assert_raise(exception, msg || proc {"Exception(#{exception}) with message matches to #{expected.inspect}"}) do + yield + end + m = ex.message + end + msg = message(msg, "") {"Expected Exception(#{exception}) was raised, but the message doesn't match"} + + if assert == :assert_equal + assert_equal(expected, m, msg) + else + msg = message(msg) { "Expected #{mu_pp expected} to match #{mu_pp m}" } + assert expected =~ m, msg + block.binding.eval("proc{|_|$~=_}").call($~) + end + ex + end + + MINI_DIR = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), "minitest") #:nodoc: + + # :call-seq: + # assert(test, [failure_message]) + # + #Tests if +test+ is true. + # + #+msg+ may be a String or a Proc. If +msg+ is a String, it will be used + #as the failure message. Otherwise, the result of calling +msg+ will be + #used as the message if the assertion fails. + # + #If no +msg+ is given, a default message will be used. + # + # assert(false, "This was expected to be true") + def assert(test, *msgs) + case msg = msgs.first + when String, Proc + when nil + msgs.shift + else + bt = caller.reject { |s| s.start_with?(MINI_DIR) } + raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt + end unless msgs.empty? + super + end + + # :call-seq: + # assert_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object responds to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_respond_to("hello", :reverse) #Succeeds + # assert_respond_to("hello", :does_not_exist) #Fails + def assert_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}#{" privately" if priv[0]}" + } + return assert obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return if obj.respond_to?(meth) + end + super(obj, meth, msg) + end + + # :call-seq: + # assert_not_respond_to( object, method, failure_message = nil ) + # + #Tests if the given Object does not respond to +method+. + # + #An optional failure message may be provided as the final argument. + # + # assert_not_respond_to("hello", :reverse) #Fails + # assert_not_respond_to("hello", :does_not_exist) #Succeeds + def assert_not_respond_to(obj, (meth, *priv), msg = nil) + unless priv.empty? + msg = message(msg) { + "Expected #{mu_pp(obj)} (#{obj.class}) to not respond to ##{meth}#{" privately" if priv[0]}" + } + return assert !obj.respond_to?(meth, *priv), msg + end + #get rid of overcounting + if caller_locations(1, 1)[0].path.start_with?(MINI_DIR) + return unless obj.respond_to?(meth) + end + refute_respond_to(obj, meth, msg) + end + + # pattern_list is an array which contains regexp and :*. + # :* means any sequence. + # + # pattern_list is anchored. + # Use [:*, regexp, :*] for non-anchored match. + def assert_pattern_list(pattern_list, actual, message=nil) + rest = actual + anchored = true + pattern_list.each_with_index {|pattern, i| + if pattern == :* + anchored = false + else + if anchored + match = /\A#{pattern}/.match(rest) + else + match = pattern.match(rest) + end + unless match + msg = message(msg) { + expect_msg = "Expected #{mu_pp pattern}\n" + if /\n[^\n]/ =~ rest + actual_mesg = +"to match\n" + rest.scan(/.*\n+/) { + actual_mesg << ' ' << $&.inspect << "+\n" + } + actual_mesg.sub!(/\+\n\z/, '') + else + actual_mesg = "to match " + mu_pp(rest) + end + actual_mesg << "\nafter #{i} patterns with #{actual.length - rest.length} characters" + expect_msg + actual_mesg + } + assert false, msg + end + rest = match.post_match + anchored = true + end + } + if anchored + assert_equal("", rest) + end + end + + def assert_warning(pat, msg = nil) + result = nil + stderr = EnvUtil.with_default_internal(pat.encoding) { + EnvUtil.verbose_warning { + result = yield + } + } + msg = message(msg) {diff pat, stderr} + assert(pat === stderr, msg) + result + end + + def assert_warn(*args) + assert_warning(*args) {$VERBOSE = false; yield} + end + + def assert_deprecated_warning(mesg = /deprecated/) + assert_warning(mesg) do + Warning[:deprecated] = true + yield + end + end + + def assert_deprecated_warn(mesg = /deprecated/) + assert_warn(mesg) do + Warning[:deprecated] = true + yield + end + end + + class << (AssertFile = Struct.new(:failure_message).new) + include CoreAssertions + def assert_file_predicate(predicate, *args) + if /\Anot_/ =~ predicate + predicate = $' + neg = " not" + end + result = File.__send__(predicate, *args) + result = !result if neg + mesg = "Expected file ".dup << args.shift.inspect + mesg << "#{neg} to be #{predicate}" + mesg << mu_pp(args).sub(/\A\[(.*)\]\z/m, '(\1)') unless args.empty? + mesg << " #{failure_message}" if failure_message + assert(result, mesg) + end + alias method_missing assert_file_predicate + + def for(message) + clone.tap {|a| a.failure_message = message} + end + end + + class AllFailures + attr_reader :failures + + def initialize + @count = 0 + @failures = {} + end + + def for(key) + @count += 1 + yield + rescue Exception => e + @failures[key] = [@count, e] + end + + def foreach(*keys) + keys.each do |key| + @count += 1 + begin + yield key + rescue Exception => e + @failures[key] = [@count, e] + end + end + end + + def message + i = 0 + total = @count.to_s + fmt = "%#{total.size}d" + @failures.map {|k, (n, v)| + v = v.message + "\n#{i+=1}. [#{fmt%n}/#{total}] Assertion for #{k.inspect}\n#{v.b.gsub(/^/, ' | ').force_encoding(v.encoding)}" + }.join("\n") + end + + def pass? + @failures.empty? + end + end + + # threads should respond to shift method. + # Array can be used. + def assert_join_threads(threads, message = nil) + errs = [] + values = [] + while th = threads.shift + begin + values << th.value + rescue Exception + errs << [th, $!] + th = nil + end + end + values + ensure + if th&.alive? + th.raise(Timeout::Error.new) + th.join rescue errs << [th, $!] + end + if !errs.empty? + msg = "exceptions on #{errs.length} threads:\n" + + errs.map {|t, err| + "#{t.inspect}:\n" + + RUBY_VERSION >= "2.5.0" ? err.full_message(highlight: false, order: :top) : err.message + }.join("\n---\n") + if message + msg = "#{message}\n#{msg}" + end + raise MiniTest::Assertion, msg + end + end + + def assert_all_assertions(msg = nil) + all = AllFailures.new + yield all + ensure + assert(all.pass?, message(msg) {all.message.chomp(".")}) + end + alias all_assertions assert_all_assertions + + def message(msg = nil, *args, &default) # :nodoc: + if Proc === msg + super(nil, *args) do + ary = [msg.call, (default.call if default)].compact.reject(&:empty?) + if 1 < ary.length + ary[0...-1] = ary[0...-1].map {|str| str.sub(/(? Date: Tue, 2 Mar 2021 22:06:56 +0900 Subject: [PATCH 099/144] use pend instead of skip --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index f78663f1..97dce90a 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - skip unless defined? Ractor + pend unless defined? Ractor end def test_ractor_shareable From 8192ef6d9d11142bdcba2e7ea7103cc301bfa353 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Mon, 8 Mar 2021 10:15:39 +0900 Subject: [PATCH 100/144] Update the latest version of CoreAssertions --- test/lib/core_assertions.rb | 3 ++- test/lib/envutil.rb | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index 118c0d11..ff78c2bd 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -260,6 +260,7 @@ def assert_ruby_status(args, test_stdin="", message=nil, **opt) ABORT_SIGNALS = Signal.list.values_at(*%w"ILL ABRT BUS SEGV TERM") def separated_runner(out = nil) + include(*Test::Unit::TestCase.ancestors.select {|c| !c.is_a?(Class) }) out = out ? IO.new(out, 'w') : STDOUT at_exit { out.puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" @@ -278,7 +279,7 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o capture_stdout = false opt[:out] = MiniTest::Unit.output if defined?(MiniTest::Unit) res_p, res_c = IO.pipe - opt[res_c.fileno] = res_c.fileno + opt[:ios] = [res_c] end src = < Date: Tue, 1 Jun 2021 21:32:16 +0900 Subject: [PATCH 101/144] Use omit_unless instead of pend unless --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 97dce90a..7ec7974e 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - pend unless defined? Ractor + omit_unless(defined? Ractor) end def test_ractor_shareable From 19b81bd4fdbef1f3bd2607db9994aa0921ddddaf Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jun 2021 21:37:40 +0900 Subject: [PATCH 102/144] Drop to support Ruby 2.4. Because bigdecimal-3.0.0 is only support Ruby 2.5+ --- .github/workflows/ci.yml | 1 - bigdecimal.gemspec | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71833339..a2297f36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,6 @@ jobs: - 2.7 - 2.6 - 2.5 - - 2.4 - debug include: - { os: windows-latest , ruby: mingw } diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 36ff65a9..3b8f24dc 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -36,5 +36,5 @@ Gem::Specification.new do |s| sample/pi.rb ] - s.required_ruby_version = Gem::Requirement.new(">= 2.4.0") + s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") end From 7b6f5bfeb082db3024f6e4d71cfae1dfeded1fef Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Sep 2021 14:21:15 +0900 Subject: [PATCH 103/144] Check the prerequisites of sync_tool task --- Rakefile | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Rakefile b/Rakefile index 2a2f4da9..e8667eb2 100644 --- a/Rakefile +++ b/Rakefile @@ -45,7 +45,19 @@ task benchmark: benchmark_tasks task :sync_tool do require 'fileutils' - FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib" - FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib" + + sync_files = [ + "../ruby/tool/lib/test/unit/core_assertions.rb", + "../ruby/tool/lib/test/unit/core_assertions.rb", + "../ruby/tool/lib/envutil.rb" + ] + + unless sync_files.all? {|fn| File.file?(fn) } + $stderr.puts "ruby/ruby must be git-cloned at ../ruby before running `rake sync_tool` here." + abort + end + + sync_files.each do |file| + FileUtils.cp file, "./test/lib" + end end From 0bdf10d55d6453c40efc4473347164821866a481 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Sep 2021 15:05:44 +0900 Subject: [PATCH 104/144] Version 3.1.0-dev --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 3b8f24dc..bb4610cb 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,6 +1,6 @@ # coding: utf-8 -bigdecimal_version = '3.0.1' +bigdecimal_version = '3.1.0.dev' Gem::Specification.new do |s| s.name = "bigdecimal" From 6d510e47bce2ba4dbff4c48c26ee8d5cd8de1758 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 6 Jul 2021 00:14:58 +0900 Subject: [PATCH 105/144] Fixed 'maybe_unused' attribute ``` ../../../src/ext/bigdecimal/bigdecimal.c:303:5: error: 'maybe_unused' attribute cannot be applied to types ENTER(1); ^ ``` --- ext/bigdecimal/bigdecimal.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 704f0451..65e7c864 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -62,7 +62,11 @@ static ID id_eq; static ID id_half; /* MACRO's to guard objects from GC by keeping them in stack */ +#ifdef RBIMPL_ATTR_MAYBE_UNUSED +#define ENTER(n) RBIMPL_ATTR_MAYBE_UNUSED() volatile VALUE vStack[n];int iStack=0 +#else #define ENTER(n) volatile VALUE RB_UNUSED_VAR(vStack[n]);int iStack=0 +#endif #define PUSH(x) (vStack[iStack++] = (VALUE)(x)) #define SAVE(p) PUSH((p)->obj) #define GUARD_OBJ(p,y) ((p)=(y), SAVE(p)) From 4f1f6cb4f5e63ba6c1c121bf5bfe6191a627cc0a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Thu, 29 Apr 2021 15:12:44 +0200 Subject: [PATCH 106/144] Fix -Wundef warnings for patterns `#if HAVE` * See [Feature #17752] * Using this to detect them: git grep -P 'if\s+HAVE' | grep -Pv 'HAVE_LONG_LONG|/ChangeLog|HAVE_TYPEOF' --- ext/bigdecimal/missing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/missing.h b/ext/bigdecimal/missing.h index 11b58c09..79698491 100644 --- a/ext/bigdecimal/missing.h +++ b/ext/bigdecimal/missing.h @@ -45,7 +45,7 @@ extern "C" { # if __has_builtin(__builtin_unreachable) # define UNREACHABLE __builtin_unreachable() -# elif HAVE___ASSUME +# elif defined(HAVE___ASSUME) # define UNREACHABLE __assume(0) # else From 13b077c63c8a982d72fb7e666b481257561890c8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 22 Oct 2021 15:46:31 +0900 Subject: [PATCH 107/144] Use omit instead of omit_unless --- test/bigdecimal/test_ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/bigdecimal/test_ractor.rb b/test/bigdecimal/test_ractor.rb index 7ec7974e..798cc494 100644 --- a/test/bigdecimal/test_ractor.rb +++ b/test/bigdecimal/test_ractor.rb @@ -6,7 +6,7 @@ class TestBigDecimalRactor < Test::Unit::TestCase def setup super - omit_unless(defined? Ractor) + omit unless defined? Ractor end def test_ractor_shareable From 159af10b17d6524f29511c5dedf49ea05fa8889c Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Fri, 15 Oct 2021 14:42:49 +0200 Subject: [PATCH 108/144] VpCheckException: improve grammar I added a space before the parenthesis, too. --- ext/bigdecimal/bigdecimal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 65e7c864..ab3d8d6b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -169,13 +169,13 @@ static void VpCheckException(Real *p, bool always) { if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results to 'NaN'(Not a Number)", always); + VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); } else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to 'Infinity'", always); + VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); } else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results to '-Infinity'", always); + VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); } } From a834eb92a28c9d411aee28b78926bc7e394df298 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:29:40 +0900 Subject: [PATCH 109/144] Fix test against #196 --- test/bigdecimal/test_bigdecimal.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 49e6b2a7..e860c9d1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -948,8 +948,8 @@ def test_div BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, false) - assert_raise_with_message(FloatDomainError, "Computation results to 'Infinity'") { BigDecimal("1") / 0 } - assert_raise_with_message(FloatDomainError, "Computation results to '-Infinity'") { BigDecimal("-1") / 0 } + assert_raise_with_message(FloatDomainError, "Computation results in 'Infinity'") { BigDecimal("1") / 0 } + assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end def test_div_with_float From bdf2364a364db6c46b86bf00c4aab715b50d8fb8 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:51:21 +0900 Subject: [PATCH 110/144] CI: Stop gem install on ruby-debug, tentatively --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a2297f36..e21739c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,3 +52,4 @@ jobs: - run: rake build - run: gem install pkg/*.gem + if: ${{ matrix.ruby != 'debug' }} From b5830045298b4ea004358da9b57ea849f8c990bd Mon Sep 17 00:00:00 2001 From: Olle Jonsson Date: Tue, 9 Nov 2021 13:18:14 +0100 Subject: [PATCH 111/144] CI: Quote 3.0 to avoid YAML Float-to-String issue This helps the output of the "name" of the CI Job in the PR checks list not to say "3" when it meant "3.0". --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e21739c5..a8e603f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - macos-10.15 - windows-latest ruby: - - 3.0 + - "3.0" # Quoted to avoid 3.0 becoming "3" as a String. - 2.7 - 2.6 - 2.5 From 0f3d5d0eb7df3dd278078718c801aa15d181f51a Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 9 Nov 2021 12:56:45 +0100 Subject: [PATCH 112/144] Fix negative Bignum conversion Introduced in 4792a917d806ca1059c952f489413073ea51bf01 `rb_absint_size` return the number of bytes needed to fit the absolute integer, but negative integers need the sign, so one more bit, and potentially one more byte. --- ext/bigdecimal/bigdecimal.c | 6 +++++- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ab3d8d6b..1d829132 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2770,8 +2770,12 @@ rb_big_convert_to_BigDecimal(VALUE val, RB_UNUSED_VAR(size_t digs), int raise_ex { assert(RB_TYPE_P(val, T_BIGNUM)); - size_t size = rb_absint_size(val, NULL); + int leading_zeros; + size_t size = rb_absint_size(val, &leading_zeros); int sign = FIX2INT(rb_big_cmp(val, INT2FIX(0))); + if (sign < 0 && leading_zeros == 0) { + size += 1; + } if (size <= sizeof(long)) { if (sign < 0) { return rb_int64_convert_to_BigDecimal(NUM2LONG(val), digs, raise_exception); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index e860c9d1..8d4d2353 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2089,6 +2089,16 @@ def test_initialize_copy_dup_clone_frozen_error assert_raise(err) { bd.send(:initialize_dup, bd2) } end + def test_llong_min + # https://github.com/ruby/bigdecimal/issues/199 + # Between LLONG_MIN and -ULLONG_MAX + llong_min = -(2 ** 63 + 1) + assert_equal BigDecimal(llong_min.to_s), BigDecimal(llong_min) + + minus_ullong_max = -(2 ** 64 - 1) + assert_equal BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max) + end + def assert_no_memory_leak(code, *rest, **opt) code = "8.times {20_000.times {begin #{code}; rescue NoMemoryError; end}; GC.start}" super(["-rbigdecimal"], From aa31ef2f33e9710bf73399c8a5d84ac2929f3dce Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:08:53 +0900 Subject: [PATCH 113/144] Fix the style in test/bigdecimal/test_bigdecimal.rb --- test/bigdecimal/test_bigdecimal.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 8d4d2353..3cc851ad 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2089,14 +2089,14 @@ def test_initialize_copy_dup_clone_frozen_error assert_raise(err) { bd.send(:initialize_dup, bd2) } end - def test_llong_min + def test_llong_min_gh_200 # https://github.com/ruby/bigdecimal/issues/199 # Between LLONG_MIN and -ULLONG_MAX llong_min = -(2 ** 63 + 1) - assert_equal BigDecimal(llong_min.to_s), BigDecimal(llong_min) + assert_equal(BigDecimal(llong_min.to_s), BigDecimal(llong_min), "[GH-200]") minus_ullong_max = -(2 ** 64 - 1) - assert_equal BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max) + assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end def assert_no_memory_leak(code, *rest, **opt) From 14e35f5a70919d82a45041bbfc539497b73a34a1 Mon Sep 17 00:00:00 2001 From: Kenta Murata <3959+mrkn@users.noreply.github.com> Date: Fri, 12 Nov 2021 23:18:27 +0900 Subject: [PATCH 114/144] Use values in RbConfig::LIMITS in test --- test/bigdecimal/test_bigdecimal.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 3cc851ad..d7b245b0 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -12,7 +12,12 @@ class TestBigDecimal < Test::Unit::TestCase require 'fiddle' LONG_MAX = (1 << (Fiddle::SIZEOF_LONG*8 - 1)) - 1 LONG_MIN = [LONG_MAX + 1].pack("L!").unpack("l!")[0] + LLONG_MAX = (1 << (Fiddle::SIZEOF_LONG_LONG*8 - 1)) - 1 + LLONG_MIN = [LLONG_MAX + 1].pack("Q!").unpack("q!")[0] + ULLONG_MAX = (1 << Fiddle::SIZEOF_LONG_LONG*8) - 1 LIMITS = { + "LLONG_MIN" => LLONG_MIN, + "ULLONG_MAX" => ULLONG_MAX, "FIXNUM_MIN" => LONG_MIN / 2, "FIXNUM_MAX" => LONG_MAX / 2, "INT64_MIN" => -9223372036854775808, @@ -2092,10 +2097,9 @@ def test_initialize_copy_dup_clone_frozen_error def test_llong_min_gh_200 # https://github.com/ruby/bigdecimal/issues/199 # Between LLONG_MIN and -ULLONG_MAX - llong_min = -(2 ** 63 + 1) - assert_equal(BigDecimal(llong_min.to_s), BigDecimal(llong_min), "[GH-200]") + assert_equal(BigDecimal(LIMITS["LLONG_MIN"].to_s), BigDecimal(LIMITS["LLONG_MIN"]), "[GH-200]") - minus_ullong_max = -(2 ** 64 - 1) + minus_ullong_max = -LIMITS["ULLONG_MAX"] assert_equal(BigDecimal(minus_ullong_max.to_s), BigDecimal(minus_ullong_max), "[GH-200]") end From 31a7a3742623fb6e3f1ff8260e6957a1e5b26687 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 14 Oct 2021 13:57:36 -0500 Subject: [PATCH 115/144] Enhanced RDoc for bigdecimal.c --- ext/bigdecimal/bigdecimal.c | 266 +++++++++++++++++++++++++++--------- 1 file changed, 201 insertions(+), 65 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1d829132..22d9c5c0 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -275,11 +275,13 @@ GetVpValue(VALUE v, int must) } /* call-seq: - * BigDecimal.double_fig + * BigDecimal.double_fig + * + * Returns the number of digits a Float object is allowed to have; + * the result is system-dependent: + * + * BigDecimal.double_fig # => 16 * - * The BigDecimal.double_fig class method returns the number of digits a - * Float number is allowed to have. The result depends upon the CPU and OS - * in use. */ static VALUE BigDecimal_double_fig(VALUE self) @@ -288,9 +290,9 @@ BigDecimal_double_fig(VALUE self) } /* call-seq: - * big_decimal.precs -> array + * precs -> array * - * Returns an Array of two Integer values that represent platform-dependent + * Returns an array of two integer values that represent platform-dependent * internal storage properties. * * This method is deprecated and will be removed in the future. @@ -298,7 +300,6 @@ BigDecimal_double_fig(VALUE self) * significant digits in scientific notation, and BigDecimal#precision for * obtaining the number of digits in decimal notation. * - * BigDecimal('5').precs #=> [9, 18] */ static VALUE @@ -319,20 +320,26 @@ BigDecimal_prec(VALUE self) } /* - * call-seq: - * big_decimal.precision -> intreger + * call-seq: + * precision -> integer * - * Returns the number of decimal digits in this number. + * Returns the number of decimal digits in +self+: * - * Example: + * %w[0 1 -1e20 1e-20 Infinity -Infinity NaN].each do |s| + * precision = BigDecimal(s).precision + * puts format("%9s has precision %2d", s, precision) + * end + * + * Output: + * + * 0 has precision 0 + * 1 has precision 1 + * -1e20 has precision 21 + * 1e-20 has precision 20 + * Infinity has precision 0 + * -Infinity has precision 0 + * NaN has precision 0 * - * BigDecimal("0").precision # => 0 - * BigDecimal("1").precision # => 1 - * BigDecimal("-1e20").precision # => 21 - * BigDecimal("1e-20").precision # => 20 - * BigDecimal("Infinity").precision # => 0 - * BigDecimal("-Infinity").precision # => 0 - * BigDecimal("NaN").precision # => 0 */ static VALUE BigDecimal_precision(VALUE self) @@ -412,12 +419,18 @@ BigDecimal_n_significant_digits(VALUE self) } /* - * call-seq: hash + * call-seq: + * hash -> integer + * + * Returns the integer hash value for +self+. + * + * Two instances of \BigDecimal have the same hash value if and only if + * they have equal: * - * Creates a hash for this BigDecimal. + * - Sign. + * - Fractional part. + * - Exponent. * - * Two BigDecimals with equal sign, - * fractional part and exponent have the same hash. */ static VALUE BigDecimal_hash(VALUE self) @@ -437,16 +450,16 @@ BigDecimal_hash(VALUE self) } /* - * call-seq: _dump + * call-seq: + * _dump * - * Method used to provide marshalling support. + * Returns a string representing the marshalling of +self+. + * See module Marshal. * - * inf = BigDecimal('Infinity') - * #=> Infinity - * BigDecimal._load(inf._dump) - * #=> Infinity + * inf = BigDecimal('Infinity') # => Infinity + * dumped = inf._dump # => "9:Infinity" + * BigDecimal._load(dumped) # => Infinity * - * See the Marshal module. */ static VALUE BigDecimal_dump(int argc, VALUE *argv, VALUE self) @@ -580,42 +593,165 @@ check_rounding_mode(VALUE const v) return sw; } -/* call-seq: - * BigDecimal.mode(mode, value) - * - * Controls handling of arithmetic exceptions and rounding. If no value - * is supplied, the current value is returned. - * - * Six values of the mode parameter control the handling of arithmetic - * exceptions: - * - * BigDecimal::EXCEPTION_NaN - * BigDecimal::EXCEPTION_INFINITY - * BigDecimal::EXCEPTION_UNDERFLOW - * BigDecimal::EXCEPTION_OVERFLOW - * BigDecimal::EXCEPTION_ZERODIVIDE - * BigDecimal::EXCEPTION_ALL - * - * For each mode parameter above, if the value set is false, computation - * continues after an arithmetic exception of the appropriate type. - * When computation continues, results are as follows: - * - * EXCEPTION_NaN:: NaN - * EXCEPTION_INFINITY:: +Infinity or -Infinity - * EXCEPTION_UNDERFLOW:: 0 - * EXCEPTION_OVERFLOW:: +Infinity or -Infinity - * EXCEPTION_ZERODIVIDE:: +Infinity or -Infinity - * - * One value of the mode parameter controls the rounding of numeric values: - * BigDecimal::ROUND_MODE. The values it can take are: - * - * ROUND_UP, :up:: round away from zero - * ROUND_DOWN, :down, :truncate:: round towards zero (truncate) - * ROUND_HALF_UP, :half_up, :default:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round away from zero. (default) - * ROUND_HALF_DOWN, :half_down:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards zero. - * ROUND_HALF_EVEN, :half_even, :banker:: round towards the nearest neighbor, unless both neighbors are equidistant, in which case round towards the even neighbor (Banker's rounding) - * ROUND_CEILING, :ceiling, :ceil:: round towards positive infinity (ceil) - * ROUND_FLOOR, :floor:: round towards negative infinity (floor) +/* call-seq: + * BigDecimal.mode(mode, setting = nil) -> integer + * + * Returns an integer representing the mode settings + * for exception handling and rounding. + * + * These modes control exception handling: + * + * - \BigDecimal::EXCEPTION_NaN. + * - \BigDecimal::EXCEPTION_INFINITY. + * - \BigDecimal::EXCEPTION_UNDERFLOW. + * - \BigDecimal::EXCEPTION_OVERFLOW. + * - \BigDecimal::EXCEPTION_ZERODIVIDE. + * - \BigDecimal::EXCEPTION_ALL. + * + * Values for +setting+ for exception handling: + * + * - +true+: sets the given +mode+ to +true+. + * - +false+: sets the given +mode+ to +false+. + * - +nil+: does not modify the mode settings. + * + * You can use method BigDecimal.save_exception_mode + * to temporarily change, and then automatically restore, exception modes. + * + * For clarity, some examples below begins by setting all + * exception modes to +false+. + * + * This mode controls the way rounding is to be performed: + * + * - \BigDecimal::ROUND_MODE + * + * You can use method BigDecimal.save_rounding_mode + * to temporarily change, and then automatically restore, the rounding mode. + * + * NaNs + * + * Mode \BigDecimal::EXCEPTION_NaN controls behavior + * when a \BigDecimal NaN is created. + * Settings" + * + * - +false+ (default): Returns BigDecimal('NaN'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('NaN') # => NaN + * BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) # => 2 + * BigDecimal('NaN') # Raises FloatDomainError + * + * Infinities + * + * Mode \BigDecimal::EXCEPTION_INFINITY controls behavior + * when a \BigDecimal Infinity or -Infinity is created. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal('Infinity') # => Infinity + * BigDecimal('-Infinity') # => -Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) # => 1 + * BigDecimal('Infinity') # Raises FloatDomainError + * BigDecimal('-Infinity') # Raises FloatDomainError + * + * Underflow + * + * Mode \BigDecimal::EXCEPTION_UNDERFLOW controls behavior + * when a \BigDecimal underflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('0') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_under + * x = BigDecimal('0.1') + * 100.times { x *= x } + * end + * flow_under # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) # => 4 + * flow_under # Raises FloatDomainError + * + * Overflow + * + * Mode \BigDecimal::EXCEPTION_OVERFLOW controls behavior + * when a \BigDecimal overflow occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * def flow_over + * x = BigDecimal('10') + * 100.times { x *= x } + * end + * flow_over # => 100 + * BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) # => 1 + * flow_over # Raises FloatDomainError + * + * Zero Division + * + * Mode \BigDecimal::EXCEPTION_ZERODIVIDE controls behavior + * when a zero-division occurs. + * Settings: + * + * - +false+ (default): Returns BigDecimal('Infinity') + * or BigDecimal('-Infinity'). + * - +true+: Raises FloatDomainError. + * + * Examples: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * one = BigDecimal('1') + * zero = BigDecimal('0') + * one / zero # => Infinity + * BigDecimal.mode(BigDecimal::EXCEPTION_ZERODIVIDE, true) # => 16 + * one / zero # Raises FloatDomainError + * + * All Exceptions + * + * Mode \BigDecimal::EXCEPTION_ALL controls all of the above: + * + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, false) # => 0 + * BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) # => 23 + * + * Rounding + * + * Mode \BigDecimal::ROUND_MODE controls the way rounding is to be performed; + * its +setting+ values are: + * + * - +ROUND_UP+: Round away from zero. + * Aliased as +:up+. + * - +ROUND_DOWN+: Round toward zero. + * Aliased as +:down+ and +:truncate+. + * - +ROUND_HALF_UP+: Round toward the nearer neighbor; + * if the neighbors are equidistant, round away from zero. + * Aliased as +:half_up+ and +:default+. + * - +ROUND_HALF_DOWN+: Round toward the nearer neighbor; + * if the neighbors are equidistant, round toward from zero. + * Aliased as +:half_down+. + * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; + * if the neighbors are equidistant, round toward the even neighbor. + * Aliased as +:half_even+ and +:banker+. + * - +ROUND_CEILING+: Round toward positive infinity. + * Aliased as +:ceiling+ and +:ceil+. + * - +ROUND_FLOOR+: Round toward negative infinity. + * Aliased as +:floor:+. * */ static VALUE From 3a35f92f8b2cca70b329a851596681bb8536cdda Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 15 Oct 2021 12:27:26 -0500 Subject: [PATCH 116/144] Enhanced RDoc for bigdecimal.c --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 22d9c5c0..cee74a58 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -617,7 +617,7 @@ check_rounding_mode(VALUE const v) * You can use method BigDecimal.save_exception_mode * to temporarily change, and then automatically restore, exception modes. * - * For clarity, some examples below begins by setting all + * For clarity, some examples below begin by setting all * exception modes to +false+. * * This mode controls the way rounding is to be performed: From 681cd2d81d06c2b419eedb6be04ce7058bd5b181 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 08:46:30 -0600 Subject: [PATCH 117/144] Respond to review for #precision --- ext/bigdecimal/bigdecimal.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index cee74a58..68cb31d2 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -325,20 +325,13 @@ BigDecimal_prec(VALUE self) * * Returns the number of decimal digits in +self+: * - * %w[0 1 -1e20 1e-20 Infinity -Infinity NaN].each do |s| - * precision = BigDecimal(s).precision - * puts format("%9s has precision %2d", s, precision) - * end - * - * Output: - * - * 0 has precision 0 - * 1 has precision 1 - * -1e20 has precision 21 - * 1e-20 has precision 20 - * Infinity has precision 0 - * -Infinity has precision 0 - * NaN has precision 0 + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 * */ static VALUE From 6d69422e3795d2c09197c93b7e25d9d5a134868e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:14:09 -0600 Subject: [PATCH 118/144] Respond to review --- ext/bigdecimal/bigdecimal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 68cb31d2..ffa705da 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -292,7 +292,7 @@ BigDecimal_double_fig(VALUE self) /* call-seq: * precs -> array * - * Returns an array of two integer values that represent platform-dependent + * Returns an Array of two Integer values that represent platform-dependent * internal storage properties. * * This method is deprecated and will be removed in the future. @@ -624,7 +624,8 @@ check_rounding_mode(VALUE const v) * * Mode \BigDecimal::EXCEPTION_NaN controls behavior * when a \BigDecimal NaN is created. - * Settings" + * + * Settings: * * - +false+ (default): Returns BigDecimal('NaN'). * - +true+: Raises FloatDomainError. From 4eadcdf0a63d3bab1ec2f2b6e11f0eb4f53de84e Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:27:52 -0600 Subject: [PATCH 119/144] Respond to review --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index ffa705da..e7eb7e4b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -733,10 +733,10 @@ check_rounding_mode(VALUE const v) * Aliased as +:up+. * - +ROUND_DOWN+: Round toward zero. * Aliased as +:down+ and +:truncate+. - * - +ROUND_HALF_UP+: Round toward the nearer neighbor; + * - +ROUND_HALF_UP+: Round toward the nearest neighbor; * if the neighbors are equidistant, round away from zero. * Aliased as +:half_up+ and +:default+. - * - +ROUND_HALF_DOWN+: Round toward the nearer neighbor; + * - +ROUND_HALF_DOWN+: Round toward the nearest neighbor; * if the neighbors are equidistant, round toward from zero. * Aliased as +:half_down+. * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; From f528a0006e99c03715fad4dc009e51284368df19 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 12 Nov 2021 09:52:10 -0600 Subject: [PATCH 120/144] Respond to review --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e7eb7e4b..8a107386 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -737,7 +737,7 @@ check_rounding_mode(VALUE const v) * if the neighbors are equidistant, round away from zero. * Aliased as +:half_up+ and +:default+. * - +ROUND_HALF_DOWN+: Round toward the nearest neighbor; - * if the neighbors are equidistant, round toward from zero. + * if the neighbors are equidistant, round toward zero. * Aliased as +:half_down+. * - +ROUND_HALF_EVEN+ (Banker's rounding): Round toward the nearest neighbor; * if the neighbors are equidistant, round toward the even neighbor. From e864828b47d873c9adf34e9eeca1ef0100366b67 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 27 Oct 2021 17:31:45 +0900 Subject: [PATCH 121/144] Add tests for the issue GH-192 --- test/bigdecimal/helper.rb | 11 +++++++++++ test/bigdecimal/test_bigdecimal.rb | 14 +++++++++++++- test/bigdecimal/test_bigdecimal_util.rb | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/test/bigdecimal/helper.rb b/test/bigdecimal/helper.rb index 22b05f09..46721fb9 100644 --- a/test/bigdecimal/helper.rb +++ b/test/bigdecimal/helper.rb @@ -1,8 +1,19 @@ # frozen_string_literal: false require "test/unit" require "bigdecimal" +require 'rbconfig/sizeof' module TestBigDecimalBase + if RbConfig::SIZEOF.key?("int64_t") + SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] + BASE = 1_000_000_000 + BASE_FIG = 9 + else + SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] + BASE = 1000 + BASE_FIG = 4 + end + def setup @mode = BigDecimal.mode(BigDecimal::EXCEPTION_ALL) BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index d7b245b0..1e6c5953 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require_relative "helper" require 'bigdecimal/math' -require 'rbconfig/sizeof' class TestBigDecimal < Test::Unit::TestCase include TestBigDecimalBase @@ -101,6 +100,19 @@ def test_BigDecimal_bug7522 assert_not_same(bd, BigDecimal(bd, 1, exception: false)) end + def test_BigDecimal_issue_192 + # https://github.com/ruby/bigdecimal/issues/192 + # https://github.com/rails/rails/pull/42125 + if BASE_FIG == 9 + int = 1_000_000_000_12345_0000 + big = BigDecimal("0.100000000012345e19") + else # BASE_FIG == 4 + int = 1_0000_12_00 + big = BigDecimal("0.1000012e9") + end + assert_equal(BigDecimal(int), big, "[ruby/bigdecimal#192]") + end + def test_BigDecimal_with_invalid_string [ '', '.', 'e1', 'd1', '.e', '.d', '1.e', '1.d', '.1e', '.1d', diff --git a/test/bigdecimal/test_bigdecimal_util.rb b/test/bigdecimal/test_bigdecimal_util.rb index ffd4c5f6..2f27163e 100644 --- a/test/bigdecimal/test_bigdecimal_util.rb +++ b/test/bigdecimal/test_bigdecimal_util.rb @@ -25,6 +25,8 @@ def test_Float_to_d_without_precision assert_equal(9.05, 9.05.to_d.to_f) assert_equal("9.05", 9.05.to_d.to_s('F')) + assert_equal("65.6", 65.6.to_d.to_s("F")) + assert_equal(Math::PI, Math::PI.to_d.to_f) bug9214 = '[ruby-core:58858]' @@ -60,6 +62,19 @@ def test_Float_to_d_bug13331 "[ruby-core:80234] [Bug #13331]") end + def test_Float_to_d_issue_192 + # https://github.com/ruby/bigdecimal/issues/192 + # https://github.com/rails/rails/pull/42125 + if BASE_FIG == 9 + flo = 1_000_000_000.12345 + big = BigDecimal("0.100000000012345e10") + else # BASE_FIG == 4 + flo = 1_0000.12 + big = BigDecimal("0.1000012e5") + end + assert_equal(flo.to_d, big, "[ruby/bigdecimal#192]") + end + def test_Rational_to_d digits = 100 delta = 1.0/10**(digits) From eebc98b85a8323eddd3d371e3e85a3d09d28d093 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 18 Nov 2021 10:59:12 +0900 Subject: [PATCH 122/144] Fix trailing zeros handling in rb_uint64_convert_to_BigDecimal Fix GH-192 --- ext/bigdecimal/bigdecimal.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 8a107386..7cec33b7 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -2862,21 +2862,29 @@ rb_uint64_convert_to_BigDecimal(uint64_t uval, RB_UNUSED_VAR(size_t digs), int r } else { DECDIG buf[BIGDECIMAL_INT64_MAX_LENGTH] = {0,}; - size_t exp = 0, ntz = 0; - for (; uval > 0; ++exp) { - DECDIG r = uval % BASE; - if (r == 0) ++ntz; - buf[BIGDECIMAL_INT64_MAX_LENGTH - exp - 1] = r; + DECDIG r = uval % BASE; + size_t len = 0, ntz = 0; + if (r == 0) { + // Count and skip trailing zeros + for (; r == 0 && uval > 0; ++ntz) { + uval /= BASE; + r = uval % BASE; + } + } + for (; uval > 0; ++len) { + // Store digits + buf[BIGDECIMAL_INT64_MAX_LENGTH - len - 1] = r; uval /= BASE; + r = uval % BASE; } - const size_t len = exp - ntz; + const size_t exp = len + ntz; vp = VpAllocReal(len); vp->MaxPrec = len; vp->Prec = len; vp->exponent = exp; VpSetSign(vp, 1); - MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - exp, DECDIG, len); + MEMCPY(vp->frac, buf + BIGDECIMAL_INT64_MAX_LENGTH - len, DECDIG, len); } return BigDecimal_wrap_struct(obj, vp); From 99442c75d3c04178ec294881d6b94d8b9cf1c701 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 12:03:37 +0900 Subject: [PATCH 123/144] Use larger precision in divide for irrational or recurring results Just in case for irrational or recurring results, the precision of the quotient is set to at least more than 2*Float::DIG plus alpha. [Bug #13754] [Fix GH-94] --- ext/bigdecimal/bigdecimal.c | 13 ++++++------- test/bigdecimal/test_bigdecimal.rb | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 7cec33b7..71499a7a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1508,13 +1508,12 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) SAVE(b); *div = b; - mx = a->Prec + vabs(a->exponent); - if (mx < b->Prec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx++; /* NOTE: An additional digit is needed for the compatibility to - the version 1.2.1 and the former. */ - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ((*c), VpCreateRbObject(mx, "#0", true)); - GUARD_OBJ((*res), VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) + mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + GUARD_OBJ((*c), VpCreateRbObject(mx + 2*BASE_FIG, "#0", true)); + GUARD_OBJ((*res), VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(*c, *res, a, b); return Qnil; } diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 1e6c5953..66b58aa1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -953,9 +953,13 @@ def test_div assert_equal(2, BigDecimal("2") / 1) assert_equal(-2, BigDecimal("2") / -1) - assert_equal(BigDecimal('1486.868686869'), BigDecimal('1472.0') / BigDecimal('0.99'), '[ruby-core:59365] [#9316]') + assert_equal(BigDecimal('1486.868686869'), + (BigDecimal('1472.0') / BigDecimal('0.99')).round(9), + '[ruby-core:59365] [#9316]') - assert_equal(4.124045235, BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0')), '[#9305]') + assert_in_delta(4.124045235, + (BigDecimal('0.9932') / (700 * BigDecimal('0.344045') / BigDecimal('1000.0'))).round(9, half: :up), + 10**Float::MIN_10_EXP, '[#9305]') BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) assert_positive_zero(BigDecimal("1.0") / BigDecimal("Infinity")) @@ -969,6 +973,15 @@ def test_div assert_raise_with_message(FloatDomainError, "Computation results in '-Infinity'") { BigDecimal("-1") / 0 } end + def test_dev_precision + bug13754 = '[ruby-core:82107] [Bug #13754]' + a = BigDecimal('101') + b = BigDecimal('0.9163472602589686') + c = a/b + assert(c.precision > b.precision, + "(101/0.9163472602589686).precision >= (0.9163472602589686).precision #{bug13754}") + end + def test_div_with_float assert_kind_of(BigDecimal, BigDecimal("3") / 1.5) assert_equal(BigDecimal("0.5"), BigDecimal(1) / 2.0) From 11cb2c884087addd84007fe4f6e9cdb80852f529 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 13:49:46 +0900 Subject: [PATCH 124/144] Let BigDecimal_DoDivmod use the same precision calculation as BigDecimal_divide --- ext/bigdecimal/bigdecimal.c | 33 ++++++++++++++++++------------ test/bigdecimal/test_bigdecimal.rb | 7 +++++++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 71499a7a..0558a17f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1613,26 +1613,33 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) return Qtrue; } - mx = a->Prec + vabs(a->exponent); - if (mxPrec + vabs(b->exponent)) mx = b->Prec + vabs(b->exponent); - mx = (mx + 1) * VpBaseFig(); - GUARD_OBJ(c, VpCreateRbObject(mx, "0", true)); - GUARD_OBJ(res, VpCreateRbObject((mx+1) * 2 +(VpBaseFig() + 1), "#0", true)); + mx = (a->Prec > b->Prec) ? a->Prec : b->Prec; + mx *= BASE_FIG; + if (2*BIGDECIMAL_DOUBLE_FIGURES > mx) + mx = 2*BIGDECIMAL_DOUBLE_FIGURES; + + GUARD_OBJ(c, VpCreateRbObject(mx + 2*BASE_FIG, "0", true)); + GUARD_OBJ(res, VpCreateRbObject(mx*2 + 2*BASE_FIG, "#0", true)); VpDivd(c, res, a, b); - mx = c->Prec * (VpBaseFig() + 1); + + mx = c->Prec * BASE_FIG; GUARD_OBJ(d, VpCreateRbObject(mx, "0", true)); VpActiveRound(d, c, VP_ROUND_DOWN, 0); + VpMult(res, d, b); VpAddSub(c, a, res, -1); + if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - VpAddSub(res, d, VpOne(), -1); + /* remainder adjustment for negative case */ + VpAddSub(res, d, VpOne(), -1); GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); - VpAddSub(d, c, b, 1); - *div = res; - *mod = d; - } else { - *div = d; - *mod = c; + VpAddSub(d, c, b, 1); + *div = res; + *mod = d; + } + else { + *div = d; + *mod = c; } return Qtrue; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 66b58aa1..26d346b3 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1043,6 +1043,13 @@ def test_divmod assert_raise(ZeroDivisionError){BigDecimal("0").divmod(0)} end + def test_divmod_precision + a = BigDecimal('2e55') + b = BigDecimal('1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) + end + def test_divmod_error assert_raise(TypeError) { BigDecimal(20).divmod('2') } end From 8dc8cd339dcfe0fe357c398551819976f9f64dd4 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Fri, 22 Jan 2021 13:50:26 +0900 Subject: [PATCH 125/144] Fix the precision of the adjusted quotient --- ext/bigdecimal/bigdecimal.c | 6 ++++-- test/bigdecimal/test_bigdecimal.rb | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0558a17f..1cbdbd88 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1630,9 +1630,11 @@ BigDecimal_DoDivmod(VALUE self, VALUE r, Real **div, Real **mod) VpAddSub(c, a, res, -1); if (!VpIsZero(c) && (VpGetSign(a) * VpGetSign(b) < 0)) { - /* remainder adjustment for negative case */ + /* result adjustment for negative case */ + res = VpReallocReal(res, d->MaxPrec); + res->MaxPrec = d->MaxPrec; VpAddSub(res, d, VpOne(), -1); - GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b)*(VpBaseFig() + 1), "0", true)); + GUARD_OBJ(d, VpCreateRbObject(GetAddSubPrec(c, b) * 2*BASE_FIG, "0", true)); VpAddSub(d, c, b, 1); *div = res; *mod = d; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 26d346b3..784560d1 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1048,6 +1048,10 @@ def test_divmod_precision b = BigDecimal('1.23456789e10') q, r = a.divmod(b) assert_equal((a/b), q) + + b = BigDecimal('-1.23456789e10') + q, r = a.divmod(b) + assert_equal((a/b), q) end def test_divmod_error From 252748de177e120920ca4fa1512e74ee7e809fd3 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Wed, 24 Nov 2021 00:20:42 +0900 Subject: [PATCH 126/144] Keep obj-to-Real link when VpReallocReal returns different pointer --- ext/bigdecimal/bigdecimal.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 1cbdbd88..668d9d1d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -869,7 +869,18 @@ VpCreateRbObject(size_t mx, const char *str, bool raise_exception) } #define VpAllocReal(prec) (Real *)VpMemAlloc(offsetof(Real, frac) + (prec) * sizeof(DECDIG)) -#define VpReallocReal(ptr, prec) (Real *)VpMemRealloc((ptr), offsetof(Real, frac) + (prec) * sizeof(DECDIG)) + +static Real * +VpReallocReal(Real *pv, size_t prec) +{ + VALUE obj = pv ? pv->obj : 0; + Real *new_pv = (Real *)VpMemRealloc(pv, offsetof(Real, frac) + prec * sizeof(DECDIG)); + if (obj) { + new_pv->obj = 0; + BigDecimal_wrap_struct(obj, new_pv); + } + return new_pv; +} static Real * VpCopy(Real *pv, Real const* const x) From 7d198394a21135a34957243c7fcca43e6b6aad00 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 25 Nov 2021 14:55:29 +0900 Subject: [PATCH 127/144] Fix BigDecimal#precision for single DECDIG case Fix GH-205 --- ext/bigdecimal/bigdecimal.c | 22 ++++++++++++++++++---- test/bigdecimal/test_bigdecimal.rb | 16 ++++++++-------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 668d9d1d..a8c0a48a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -341,11 +341,19 @@ BigDecimal_precision(VALUE self) Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); + if (VpIsZero(p) || !VpIsDef(p)) return INT2FIX(0); /* * The most significant digit is frac[0], and the least significant digit is frac[Prec-1]. * When the exponent is zero, the decimal point is located just before frac[0]. + * + * * When the exponent is negative, the decimal point moves to leftward. + * In this case, the precision can be calculated by BASE_FIG * (-exponent + Prec) - ntz. + * + * 0 . 0000 0000 | frac[0] frac[1] ... frac[Prec-1] + * <----------| exponent == -2 + * * Conversely, when the exponent is positive, the decimal point moves to rightward. * * | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1] @@ -353,24 +361,30 @@ BigDecimal_precision(VALUE self) */ ssize_t ex = p->exponent; - ssize_t precision = 0; + + /* Count the number of decimal digits before frac[1]. */ + ssize_t precision = BASE_FIG; /* The number of decimal digits in frac[0]. */ if (ex < 0) { - precision = (-ex + 1) * BASE_FIG; /* 1 is for p->frac[0] */ + precision += -ex * BASE_FIG; /* The number of leading zeros before frac[0]. */ ex = 0; } - else if (p->Prec > 0) { + else if (ex > 0) { + /* Count the number of decimal digits without the leading zeros in + * the most significant digit in the integral part. */ DECDIG x = p->frac[0]; for (precision = 0; x > 0; x /= 10) { ++precision; } } + /* Count the number of decimal digits after frac[0]. */ if (ex > (ssize_t)p->Prec) { + /* In this case the number is an integer with multiple trailing zeros. */ precision += (ex - 1) * BASE_FIG; } else if (p->Prec > 0) { ssize_t n = (ssize_t)p->Prec - 1; - while (n > 0 && p->frac[n] == 0) --n; + while (n > 0 && p->frac[n] == 0) --n; /* Skip trailing zeros, just in case. */ precision += n * BASE_FIG; diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 784560d1..5a4108c6 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2036,10 +2036,14 @@ def test_precision_only_integer def test_precision_only_fraction assert_equal(1, BigDecimal("0.1").precision) assert_equal(1, BigDecimal("-0.1").precision) - assert_equal(1, BigDecimal("0.01").precision) - assert_equal(1, BigDecimal("-0.01").precision) + assert_equal(2, BigDecimal("0.01").precision) + assert_equal(2, BigDecimal("-0.01").precision) assert_equal(2, BigDecimal("0.11").precision) assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(9, BigDecimal("0.000_000_001").precision) + assert_equal(9, BigDecimal("-0.000_000_001").precision) + assert_equal(10, BigDecimal("0.000_000_000_1").precision) + assert_equal(10, BigDecimal("-0.000_000_000_1").precision) assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").precision) assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").precision) assert_equal(100, BigDecimal("111e-100").precision) @@ -2047,12 +2051,8 @@ def test_precision_only_fraction end def test_precision_full - assert_equal(1, BigDecimal("0.1").precision) - assert_equal(1, BigDecimal("-0.1").precision) - assert_equal(1, BigDecimal("0.01").precision) - assert_equal(1, BigDecimal("-0.01").precision) - assert_equal(2, BigDecimal("0.11").precision) - assert_equal(2, BigDecimal("-0.11").precision) + assert_equal(5, BigDecimal("11111e-2").precision) + assert_equal(5, BigDecimal("-11111e-2").precision) assert_equal(5, BigDecimal("11111e-2").precision) assert_equal(5, BigDecimal("-11111e-2").precision) assert_equal(21, BigDecimal("100.000_000_000_000_000_001").precision) From 6139ea1092861b74fda6172120cffe9bfe793627 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Nov 2021 11:53:55 -0600 Subject: [PATCH 128/144] Enhanced RDoc for selected methods --- ext/bigdecimal/bigdecimal.c | 53 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index a8c0a48a..f2b93ccc 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1121,12 +1121,14 @@ BigDecimal_coerce(VALUE self, VALUE other) } /* - * call-seq: - * +big_decimal -> big_decimal + * call-seq: + * +big_decimal -> big_decimal + * + * Returns +self+: * - * Return self. + * +BigDecimal(5) # => 0.5e1 + * +BigDecimal(-5) # => -0.5e1 * - * +BigDecimal('5') #=> 0.5e1 */ static VALUE @@ -1136,22 +1138,16 @@ BigDecimal_uplus(VALUE self) } /* - * Document-method: BigDecimal#add - * Document-method: BigDecimal#+ + * call-seq: + * self + value -> new_bigdecimal * - * call-seq: - * add(value, digits) + * Returns the sum of +self+ and +value+: * - * Add the specified value. - * - * e.g. - * c = a.add(b,n) - * c = a + b + * b = BigDecimal('111111.111') # => 0.111111111e6 + * b + 1 # => 0.111112111e6 * - * digits:: If specified and less than the number of significant digits of the - * result, the result is rounded to that number of digits, according - * to BigDecimal.mode. */ + static VALUE BigDecimal_add(VALUE self, VALUE r) { @@ -1882,6 +1878,31 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) return BigDecimal_div2(self, b, n); } + /* + * call-seq: + * add(value, ndigits) + * + * Returns the sum of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 + * BigDecimal('111111.111').add(1, 2) # => 0.11e6 + * BigDecimal('111111.111').add(1, 3) # => 0.111e6 + * BigDecimal('111111.111').add(1, 4) # => 0.1111e6 + * BigDecimal('111111.111').add(1, 5) # => 0.11111e6 + * BigDecimal('111111.111').add(1, 6) # => 0.111112e6 + * BigDecimal('111111.111').add(1, 7) # => 0.1111121e6 + * BigDecimal('111111.111').add(1, 8) # => 0.11111211e6 + * BigDecimal('111111.111').add(1, 9) # => 0.111112111e6 + * + */ + static VALUE BigDecimal_add2(VALUE self, VALUE b, VALUE n) { From 0de9298d15698058c7cc9b5b2a4905a76e4c8e1f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Fri, 19 Nov 2021 12:07:34 -0600 Subject: [PATCH 129/144] Enhanced RDoc for selected methods --- ext/bigdecimal/bigdecimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index f2b93ccc..0157ef53 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1122,7 +1122,7 @@ BigDecimal_coerce(VALUE self, VALUE other) /* * call-seq: - * +big_decimal -> big_decimal + * +big_decimal -> self * * Returns +self+: * @@ -1880,7 +1880,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) /* * call-seq: - * add(value, ndigits) + * add(value, ndigits) -> new_bigdecimal * * Returns the sum of +self+ and +value+ * with a precision of +ndigits+ decimal digits. From 42c999f728ba36c364a7583e292c95ab9f77260f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Tue, 23 Nov 2021 13:26:14 -0600 Subject: [PATCH 130/144] Set rounding mode in example --- ext/bigdecimal/bigdecimal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 0157ef53..6c845fcd 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1891,6 +1891,8 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * * Examples: * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 * BigDecimal('111111.111').add(1, 2) # => 0.11e6 * BigDecimal('111111.111').add(1, 3) # => 0.111e6 From 8fc83dd2fe9fb9afa4213d5b007d455dd2b6eb3f Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 25 Nov 2021 10:42:34 -0600 Subject: [PATCH 131/144] Set rounding mode in example --- ext/bigdecimal/bigdecimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 6c845fcd..e0496c2d 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1892,7 +1892,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * Examples: * * # Set the rounding mode. - * BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 * BigDecimal('111111.111').add(1, 2) # => 0.11e6 * BigDecimal('111111.111').add(1, 3) # => 0.111e6 From 4fbec55680e95692ebd24f21f873d39de3a2e91b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 25 Nov 2021 14:51:13 +0900 Subject: [PATCH 132/144] Add BigDecimal#scale Fixes GH-198. --- ext/bigdecimal/bigdecimal.c | 201 ++++++++++++++++++++--------- test/bigdecimal/test_bigdecimal.rb | 53 ++++++++ 2 files changed, 195 insertions(+), 59 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index e0496c2d..fa7ae62f 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -319,86 +319,168 @@ BigDecimal_prec(VALUE self) return obj; } -/* - * call-seq: - * precision -> integer - * - * Returns the number of decimal digits in +self+: - * - * BigDecimal("0").precision # => 0 - * BigDecimal("1").precision # => 1 - * BigDecimal("-1e20").precision # => 21 - * BigDecimal("1e-20").precision # => 20 - * BigDecimal("Infinity").precision # => 0 - * BigDecimal("-Infinity").precision # => 0 - * BigDecimal("NaN").precision # => 0 - * - */ -static VALUE -BigDecimal_precision(VALUE self) +static void +BigDecimal_count_precision_and_scale(VALUE self, ssize_t *out_precision, ssize_t *out_scale) { ENTER(1); + if (out_precision == NULL && out_scale == NULL) + return; + Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); - if (VpIsZero(p) || !VpIsDef(p)) return INT2FIX(0); + if (VpIsZero(p) || !VpIsDef(p)) { + zero: + if (out_precision) *out_precision = 0; + if (out_scale) *out_scale = 0; + return; + } + + DECDIG x; + + ssize_t n = p->Prec; /* The length of frac without zeros. */ + while (n > 0 && p->frac[n-1] == 0) --n; + if (n == 0) goto zero; + + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; + + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; /* - * The most significant digit is frac[0], and the least significant digit is frac[Prec-1]. - * When the exponent is zero, the decimal point is located just before frac[0]. + * Calculate the precision and the scale + * ------------------------------------- * + * The most significant digit is frac[0], and the least significant digit + * is frac[Prec-1]. When the exponent is zero, the decimal point is + * located just before frac[0]. * * When the exponent is negative, the decimal point moves to leftward. - * In this case, the precision can be calculated by BASE_FIG * (-exponent + Prec) - ntz. + * In this case, the precision can be calculated by + * + * precision = BASE_FIG * (-exponent + n) - ntz, + * + * and the scale is the same as precision. * - * 0 . 0000 0000 | frac[0] frac[1] ... frac[Prec-1] - * <----------| exponent == -2 + * 0 . 0000 0000 | frac[0] ... frac[n-1] | + * |<----------| exponent == -2 | + * |---------------------------------->| precision + * |---------------------------------->| scale * - * Conversely, when the exponent is positive, the decimal point moves to rightward. * - * | frac[0] frac[1] frac[2] . frac[3] frac[4] ... frac[Prec-1] - * |------------------------> exponent == 3 + * Conversely, when the exponent is positive, the decimal point moves to + * rightward. In this case, the scale equals to + * + * BASE_FIG * (n - exponent) - ntz. + * + * the precision equals to + * + * scale + BASE_FIG * exponent - nlz. + * + * | frac[0] frac[1] . frac[2] ... frac[n-1] | + * |---------------->| exponent == 2 | + * | |---------------------->| scale + * |---------------------------------------->| precision */ ssize_t ex = p->exponent; /* Count the number of decimal digits before frac[1]. */ - ssize_t precision = BASE_FIG; /* The number of decimal digits in frac[0]. */ + ssize_t n_digits_head = BASE_FIG; if (ex < 0) { - precision += -ex * BASE_FIG; /* The number of leading zeros before frac[0]. */ - ex = 0; + n_digits_head += (-ex) * BASE_FIG; /* The number of leading zeros before frac[0]. */ + ex = 0; } else if (ex > 0) { - /* Count the number of decimal digits without the leading zeros in - * the most significant digit in the integral part. */ - DECDIG x = p->frac[0]; - for (precision = 0; x > 0; x /= 10) { - ++precision; - } + /* Count the number of decimal digits without the leading zeros in + * the most significant digit in the integral part. + */ + n_digits_head -= nlz; /* Make the number of digits */ } - /* Count the number of decimal digits after frac[0]. */ - if (ex > (ssize_t)p->Prec) { - /* In this case the number is an integer with multiple trailing zeros. */ - precision += (ex - 1) * BASE_FIG; + if (out_precision) { + ssize_t precision = n_digits_head; + + /* Count the number of decimal digits after frac[0]. */ + if (ex > (ssize_t)n) { + /* In this case the number is an integer with some trailing zeros. */ + precision += (ex - 1) * BASE_FIG; + } + else if (n > 0) { + precision += (n - 1) * BASE_FIG; + + if (ex < (ssize_t)n) { + precision -= ntz; + } + } + + *out_precision = precision; } - else if (p->Prec > 0) { - ssize_t n = (ssize_t)p->Prec - 1; - while (n > 0 && p->frac[n] == 0) --n; /* Skip trailing zeros, just in case. */ - precision += n * BASE_FIG; + if (out_scale) { + ssize_t scale = 0; - if (ex < (ssize_t)p->Prec) { - DECDIG x = p->frac[n]; - for (; x > 0 && x % 10 == 0; x /= 10) { - --precision; - } + if (p->exponent < 0) { + scale = n_digits_head + (n - 1) * BASE_FIG - ntz; + } + else if (n > p->exponent) { + scale = (n - p->exponent) * BASE_FIG - ntz; } + + *out_scale = scale; } +} +/* + * call-seq: + * precision -> integer + * + * Returns the number of decimal digits in +self+: + * + * BigDecimal("0").precision # => 0 + * BigDecimal("1").precision # => 1 + * BigDecimal("1.1").precision # => 2 + * BigDecimal("3.1415").precision # => 5 + * BigDecimal("-1e20").precision # => 21 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").precision # => 0 + * BigDecimal("-Infinity").precision # => 0 + * BigDecimal("NaN").precision # => 0 + * + */ +static VALUE +BigDecimal_precision(VALUE self) +{ + ssize_t precision; + BigDecimal_count_precision_and_scale(self, &precision, NULL); return SSIZET2NUM(precision); } +/* + * call-seq: + * scale -> integer + * + * Returns the number of decimal digits following the decimal digits in +self+. + * + * BigDecimal("0").scale # => 0 + * BigDecimal("1").scale # => 1 + * BigDecimal("1.1").scale # => 1 + * BigDecimal("3.1415").scale # => 4 + * BigDecimal("-1e20").precision # => 0 + * BigDecimal("1e-20").precision # => 20 + * BigDecimal("Infinity").scale # => 0 + * BigDecimal("-Infinity").scale # => 0 + * BigDecimal("NaN").scale # => 0 + */ +static VALUE +BigDecimal_scale(VALUE self) +{ + ssize_t scale; + BigDecimal_count_precision_and_scale(self, NULL, &scale); + return SSIZET2NUM(scale); +} + static VALUE BigDecimal_n_significant_digits(VALUE self) { @@ -406,23 +488,23 @@ BigDecimal_n_significant_digits(VALUE self) Real *p; GUARD_OBJ(p, GetVpValue(self, 1)); - - ssize_t n = p->Prec; - while (n > 0 && p->frac[n-1] == 0) --n; - if (n <= 0) { + if (VpIsZero(p) || !VpIsDef(p)) { return INT2FIX(0); } - int nlz, ntz; + ssize_t n = p->Prec; /* The length of frac without trailing zeros. */ + for (n = p->Prec; n > 0 && p->frac[n-1] == 0; --n); + if (n == 0) return INT2FIX(0); - DECDIG x = p->frac[0]; - for (nlz = BASE_FIG; x > 0; x /= 10) --nlz; + DECDIG x; + int nlz = BASE_FIG; + for (x = p->frac[0]; x > 0; x /= 10) --nlz; - x = p->frac[n-1]; - for (ntz = 0; x > 0 && x % 10 == 0; x /= 10) ++ntz; + int ntz = 0; + for (x = p->frac[n-1]; x > 0 && x % 10 == 0; x /= 10) ++ntz; - ssize_t n_digits = BASE_FIG * n - nlz - ntz; - return SSIZET2NUM(n_digits); + ssize_t n_significant_digits = BASE_FIG*n - nlz - ntz; + return SSIZET2NUM(n_significant_digits); } /* @@ -4129,6 +4211,7 @@ Init_bigdecimal(void) /* instance methods */ rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); + rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 5a4108c6..f2162caf 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2070,6 +2070,59 @@ def test_precision_special end end + def test_scale_only_integer + assert_equal(0, BigDecimal(0).scale) + assert_equal(0, BigDecimal(1).scale) + assert_equal(0, BigDecimal(-1).scale) + assert_equal(0, BigDecimal(10).scale) + assert_equal(0, BigDecimal(-10).scale) + assert_equal(0, BigDecimal(100_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000).scale) + assert_equal(0, BigDecimal(100_000_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000_000).scale) + assert_equal(0, BigDecimal(100_000_000_000_000_000_000).scale) + assert_equal(0, BigDecimal(-100_000_000_000_000_000_000).scale) + assert_equal(0, BigDecimal("111e100").scale) + assert_equal(0, BigDecimal("-111e100").scale) + end + + def test_scale_only_fraction + assert_equal(1, BigDecimal("0.1").scale) + assert_equal(1, BigDecimal("-0.1").scale) + assert_equal(2, BigDecimal("0.01").scale) + assert_equal(2, BigDecimal("-0.01").scale) + assert_equal(2, BigDecimal("0.11").scale) + assert_equal(2, BigDecimal("-0.11").scale) + assert_equal(21, BigDecimal("0.000_000_000_000_000_000_001").scale) + assert_equal(21, BigDecimal("-0.000_000_000_000_000_000_001").scale) + assert_equal(100, BigDecimal("111e-100").scale) + assert_equal(100, BigDecimal("-111e-100").scale) + end + + def test_scale_full + assert_equal(1, BigDecimal("0.1").scale) + assert_equal(1, BigDecimal("-0.1").scale) + assert_equal(2, BigDecimal("0.01").scale) + assert_equal(2, BigDecimal("-0.01").scale) + assert_equal(2, BigDecimal("0.11").scale) + assert_equal(2, BigDecimal("-0.11").scale) + assert_equal(2, BigDecimal("11111e-2").scale) + assert_equal(2, BigDecimal("-11111e-2").scale) + assert_equal(18, BigDecimal("100.000_000_000_000_000_001").scale) + assert_equal(18, BigDecimal("-100.000_000_000_000_000_001").scale) + end + + def test_scale_special + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) + + assert_equal(0, BigDecimal("Infinity").scale) + assert_equal(0, BigDecimal("-Infinity").scale) + assert_equal(0, BigDecimal("NaN").scale) + end + end + def test_n_significant_digits_only_integer assert_equal(0, BigDecimal(0).n_significant_digits) assert_equal(1, BigDecimal(1).n_significant_digits) From ceaf16b03eecc6402583ac5a0e47b5831f6cc804 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 2 Dec 2021 17:03:33 +0900 Subject: [PATCH 133/144] [Doc] Add documentation of BigDecimal#n_significant_digits --- ext/bigdecimal/bigdecimal.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index fa7ae62f..70e1bf7e 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -481,6 +481,22 @@ BigDecimal_scale(VALUE self) return SSIZET2NUM(scale); } +/* + * call-seq: + * n_significant_digits -> integer + * + * Returns the number of decimal significant digits in +self+. + * + * BigDecimal("0").scale # => 0 + * BigDecimal("1").scale # => 1 + * BigDecimal("1.1").scale # => 2 + * BigDecimal("3.1415").scale # => 5 + * BigDecimal("-1e20").precision # => 1 + * BigDecimal("1e-20").precision # => 1 + * BigDecimal("Infinity").scale # => 0 + * BigDecimal("-Infinity").scale # => 0 + * BigDecimal("NaN").scale # => 0 + */ static VALUE BigDecimal_n_significant_digits(VALUE self) { From c019caeabaf70ef9a8c5c6f49218ff8818ce5e4b Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 2 Dec 2021 17:06:52 +0900 Subject: [PATCH 134/144] Add BigDecimal#precision_scale --- ext/bigdecimal/bigdecimal.c | 19 +++++++++++++++++++ test/bigdecimal/test_bigdecimal.rb | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 70e1bf7e..47b10d6a 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -481,6 +481,24 @@ BigDecimal_scale(VALUE self) return SSIZET2NUM(scale); } +/* + * call-seq: + * precision_scale -> [integer, integer] + * + * Returns a 2-length array; the first item is the result of + * BigDecimal#precision and the second one is of BigDecimal#scale. + * + * See BigDecimal#precision. + * See BigDecimal#scale. + */ +static VALUE +BigDecimal_precision_scale(VALUE self) +{ + ssize_t precision, scale; + BigDecimal_count_precision_and_scale(self, &precision, &scale); + return rb_assoc_new(SSIZET2NUM(precision), SSIZET2NUM(scale)); +} + /* * call-seq: * n_significant_digits -> integer @@ -4228,6 +4246,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "precs", BigDecimal_prec, 0); rb_define_method(rb_cBigDecimal, "precision", BigDecimal_precision, 0); rb_define_method(rb_cBigDecimal, "scale", BigDecimal_scale, 0); + rb_define_method(rb_cBigDecimal, "precision_scale", BigDecimal_precision_scale, 0); rb_define_method(rb_cBigDecimal, "n_significant_digits", BigDecimal_n_significant_digits, 0); rb_define_method(rb_cBigDecimal, "add", BigDecimal_add2, 2); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index f2162caf..273abf78 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -2123,6 +2123,17 @@ def test_scale_special end end + def test_precision_scale + assert_equal([2, 0], BigDecimal("11.0").precision_scale) + assert_equal([2, 1], BigDecimal("1.1").precision_scale) + assert_equal([2, 2], BigDecimal("0.11").precision_scale) + + BigDecimal.save_exception_mode do + BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, false) + assert_equal([0, 0], BigDecimal("Infinity").precision_scale) + end + end + def test_n_significant_digits_only_integer assert_equal(0, BigDecimal(0).n_significant_digits) assert_equal(1, BigDecimal(1).n_significant_digits) From bccaa66f2ca868c80e79946a98edc9f006d9658f Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 2 Dec 2021 12:02:21 +0100 Subject: [PATCH 135/144] Improve extconf to allow using bigdecimal as a git gem e.g. ``` gem "bigdecimal", github: "ruby/bigdecimal" ``` It would fail because bundler regenerates the `gemspec`, so `bigdecimal_version` is gone. --- bigdecimal.gemspec | 4 +--- ext/bigdecimal/extconf.rb | 5 +---- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index bb4610cb..1d8b36ee 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -1,10 +1,8 @@ # coding: utf-8 -bigdecimal_version = '3.1.0.dev' - Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = bigdecimal_version + s.version = "3.1.0.dev" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"] diff --git a/ext/bigdecimal/extconf.rb b/ext/bigdecimal/extconf.rb index c92aacb3..9b0c55b2 100644 --- a/ext/bigdecimal/extconf.rb +++ b/ext/bigdecimal/extconf.rb @@ -3,10 +3,7 @@ def check_bigdecimal_version(gemspec_path) message "checking RUBY_BIGDECIMAL_VERSION... " - - bigdecimal_version = - IO.readlines(gemspec_path) - .grep(/\Abigdecimal_version\s+=\s+/)[0][/\'([^\']+)\'/, 1] + bigdecimal_version = File.read(gemspec_path).match(/^\s*s\.version\s+=\s+['"]([^'"]+)['"]\s*$/)[1] version_components = bigdecimal_version.split('.') bigdecimal_version = version_components[0, 3].join('.') From 900bb7fcf52e046a2d22934383431a4406c9d299 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 21:35:33 +0900 Subject: [PATCH 136/144] Allow passing both float and precision in BigDecimal#div Fix GH-212. --- ext/bigdecimal/bigdecimal.c | 6 +++++- test/bigdecimal/test_bigdecimal.rb | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 47b10d6a..36173f35 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1938,11 +1938,15 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) Real *res = NULL; Real *av = NULL, *bv = NULL, *cv = NULL; size_t mx = ix + VpBaseFig()*2; + size_t b_prec = ix; size_t pl = VpSetPrecLimit(0); GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); - GUARD_OBJ(bv, GetVpValue(b, 1)); + if (RB_FLOAT_TYPE_P(b) && b_prec > BIGDECIMAL_DOUBLE_FIGURES) { + b_prec = BIGDECIMAL_DOUBLE_FIGURES; + } + GUARD_OBJ(bv, GetVpValueWithPrec(b, b_prec, 1)); mx = av->Prec + bv->Prec + 2; if (mx <= cv->MaxPrec) mx = cv->MaxPrec + 1; GUARD_OBJ(res, VpCreateRbObject((mx * 2 + 2)*VpBaseFig(), "#0", true)); diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 273abf78..9e8f0d59 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1091,6 +1091,16 @@ def test_div_bigdecimal end end + def test_div_bigdecimal_with_float_and_precision + x = BigDecimal(5) + y = 5.1 + assert_equal(x.div(BigDecimal(y, 0), 8), + x.div(y, 8)) + + assert_equal(x.div(BigDecimal(y, 0), 100), + x.div(y, 100)) + end + def test_abs_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(1267650600228229401496703205376, x.abs) From ef9cf4e69e810ce9bb7e57d18cce1c589dfccb28 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 21:50:46 +0900 Subject: [PATCH 137/144] Add TODO comment --- ext/bigdecimal/bigdecimal.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 36173f35..c648616b 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -1943,6 +1943,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) GUARD_OBJ(cv, VpCreateRbObject(mx + VpBaseFig(), "0", true)); GUARD_OBJ(av, GetVpValue(self, 1)); + /* TODO: I want to refactor this precision control for a float value later + * by introducing an implicit conversion function instead of + * GetVpValueWithPrec. */ if (RB_FLOAT_TYPE_P(b) && b_prec > BIGDECIMAL_DOUBLE_FIGURES) { b_prec = BIGDECIMAL_DOUBLE_FIGURES; } From 299d7e9dfd5f2a049c5fb15daa8dc2872794948a Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 14 Dec 2021 20:31:49 -0800 Subject: [PATCH 138/144] s/RubyVM::JIT/RubyVM::MJIT/g --- test/lib/core_assertions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/core_assertions.rb b/test/lib/core_assertions.rb index ff78c2bd..a4abff41 100644 --- a/test/lib/core_assertions.rb +++ b/test/lib/core_assertions.rb @@ -115,7 +115,7 @@ def syntax_check(code, fname, line) def assert_no_memory_leak(args, prepare, code, message=nil, limit: 2.0, rss: false, **opt) # TODO: consider choosing some appropriate limit for MJIT and stop skipping this once it does not randomly fail - pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? + pend 'assert_no_memory_leak may consider MJIT memory usage as leak' if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? require_relative '../../memory_status' raise MiniTest::Skip, "unsupported platform" unless defined?(Memory::Status) From ba57a569a36fe1b536879dd2664e4ba27212bfc6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 16 Dec 2021 13:58:08 +0900 Subject: [PATCH 139/144] Skip installation test with Windows --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8e603f6..87658190 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,4 +52,4 @@ jobs: - run: rake build - run: gem install pkg/*.gem - if: ${{ matrix.ruby != 'debug' }} + if: ${{ ( matrix.ruby != 'debug' && matrix.os == 'linux-latest' ) || ( matrix.ruby != 'debug' && matrix.os == 'macos-latest' ) }} From 13e0e93f37a6f7db110d1d95ce6b3617fb0775d7 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Thu, 9 Dec 2021 22:24:12 +0900 Subject: [PATCH 140/144] Let BigDecimal#quo accept precision Fix GH-214. --- ext/bigdecimal/bigdecimal.c | 96 ++++++++++++++++++++++-------- test/bigdecimal/test_bigdecimal.rb | 24 ++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index c648616b..3be0ffec 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -115,6 +115,8 @@ static ID id_half; */ static unsigned short VpGetException(void); static void VpSetException(unsigned short f); +static void VpCheckException(Real *p, bool always); +static VALUE VpCheckGetValue(Real *p); static void VpInternalRound(Real *c, size_t ixDigit, DECDIG vPrev, DECDIG v); static int VpLimitRound(Real *c, size_t ixDigit); static Real *VpCopy(Real *pv, Real const* const x); @@ -165,27 +167,6 @@ is_kind_of_BigDecimal(VALUE const v) return rb_typeddata_is_kind_of(v, &BigDecimal_data_type); } -static void -VpCheckException(Real *p, bool always) -{ - if (VpIsNaN(p)) { - VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); - } - else if (VpIsPosInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); - } - else if (VpIsNegInf(p)) { - VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); - } -} - -static VALUE -VpCheckGetValue(Real *p) -{ - VpCheckException(p, false); - return p->obj; -} - NORETURN(static void cannot_be_coerced_into_BigDecimal(VALUE, VALUE)); static void @@ -1656,12 +1637,15 @@ BigDecimal_divide(VALUE self, VALUE r, Real **c, Real **res, Real **div) } /* call-seq: - * a / b -> bigdecimal - * quo(value) -> bigdecimal + * a / b -> bigdecimal * * Divide by the specified value. * + * The result precision will be the precision of the larger operand, + * but its minimum is 2*Float::DIG. + * * See BigDecimal#div. + * See BigDecimal#quo. */ static VALUE BigDecimal_div(VALUE self, VALUE r) @@ -1683,6 +1667,45 @@ BigDecimal_div(VALUE self, VALUE r) return VpCheckGetValue(c); } +static VALUE BigDecimal_round(int argc, VALUE *argv, VALUE self); + +/* call-seq: + * quo(value) -> bigdecimal + * quo(value, digits) -> bigdecimal + * + * Divide by the specified value. + * + * digits:: If specified and less than the number of significant digits of + * the result, the result is rounded to the given number of digits, + * according to the rounding mode indicated by BigDecimal.mode. + * + * If digits is 0 or omitted, the result is the same as for the + * / operator. + * + * See BigDecimal#/. + * See BigDecimal#div. + */ +static VALUE +BigDecimal_quo(int argc, VALUE *argv, VALUE self) +{ + VALUE value, digits, result; + SIGNED_VALUE n = -1; + + argc = rb_scan_args(argc, argv, "11", &value, &digits); + if (argc > 1) { + n = GetPrecisionInt(digits); + } + + if (n > 0) { + result = BigDecimal_div2(self, value, digits); + } + else { + result = BigDecimal_div(self, value); + } + + return result; +} + /* * %: mod = a%b = a - (a.to_f/b).floor * b * div = (a.to_f/b).floor @@ -1964,6 +1987,7 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) * Document-method: BigDecimal#div * * call-seq: + * div(value) -> integer * div(value, digits) -> bigdecimal or integer * * Divide by the specified value. @@ -1978,6 +2002,9 @@ BigDecimal_div2(VALUE self, VALUE b, VALUE n) * If digits is not specified, the result is an integer, * by analogy with Float#div; see also BigDecimal#divmod. * + * See BigDecimal#/. + * See BigDecimal#quo. + * * Examples: * * a = BigDecimal("4") @@ -4272,7 +4299,7 @@ Init_bigdecimal(void) rb_define_method(rb_cBigDecimal, "-@", BigDecimal_neg, 0); rb_define_method(rb_cBigDecimal, "*", BigDecimal_mult, 1); rb_define_method(rb_cBigDecimal, "/", BigDecimal_div, 1); - rb_define_method(rb_cBigDecimal, "quo", BigDecimal_div, 1); + rb_define_method(rb_cBigDecimal, "quo", BigDecimal_quo, -1); rb_define_method(rb_cBigDecimal, "%", BigDecimal_mod, 1); rb_define_method(rb_cBigDecimal, "modulo", BigDecimal_mod, 1); rb_define_method(rb_cBigDecimal, "remainder", BigDecimal_remainder, 1); @@ -4446,6 +4473,27 @@ VpSetException(unsigned short f) bigdecimal_set_thread_local_exception_mode(f); } +static void +VpCheckException(Real *p, bool always) +{ + if (VpIsNaN(p)) { + VpException(VP_EXCEPTION_NaN, "Computation results in 'NaN' (Not a Number)", always); + } + else if (VpIsPosInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in 'Infinity'", always); + } + else if (VpIsNegInf(p)) { + VpException(VP_EXCEPTION_INFINITY, "Computation results in '-Infinity'", always); + } +} + +static VALUE +VpCheckGetValue(Real *p) +{ + VpCheckException(p, false); + return p->obj; +} + /* * Precision limit. */ diff --git a/test/bigdecimal/test_bigdecimal.rb b/test/bigdecimal/test_bigdecimal.rb index 9e8f0d59..bbfcbec4 100644 --- a/test/bigdecimal/test_bigdecimal.rb +++ b/test/bigdecimal/test_bigdecimal.rb @@ -1101,6 +1101,30 @@ def test_div_bigdecimal_with_float_and_precision x.div(y, 100)) end + def test_quo_without_prec + x = BigDecimal(5) + y = BigDecimal(229) + assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y)) + end + + def test_quo_with_prec + begin + saved_mode = BigDecimal.mode(BigDecimal::ROUND_MODE) + BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + + x = BigDecimal(5) + y = BigDecimal(229) + assert_equal(BigDecimal("0.021834061135371179039301310043668122"), x.quo(y, 0)) + assert_equal(BigDecimal("0.022"), x.quo(y, 2)) + assert_equal(BigDecimal("0.0218"), x.quo(y, 3)) + assert_equal(BigDecimal("0.0218341"), x.quo(y, 6)) + assert_equal(BigDecimal("0.02183406114"), x.quo(y, 10)) + assert_equal(BigDecimal("0.021834061135371179039301310043668122270742358078603"), x.quo(y, 50)) + ensure + BigDecimal.mode(BigDecimal::ROUND_MODE, saved_mode) + end + end + def test_abs_bigdecimal x = BigDecimal((2**100).to_s) assert_equal(1267650600228229401496703205376, x.abs) From acabb132a4e8d8947a33ae5c2d53adc8193ff894 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 21 Dec 2021 02:17:16 -0600 Subject: [PATCH 141/144] Enhanced RDoc for BigDecimal (#209) * Enhanced RDoc for BigDecimal * Update ext/bigdecimal/bigdecimal.c Remove the instance number of `Float::DIG`. * Update ext/bigdecimal/bigdecimal.c Add BigDecimal call-seq without ndigits. * Update ext/bigdecimal/bigdecimal.c Replace the word sum with value or result in the description of BigDecimal(). * Update ext/bigdecimal/bigdecimal.c Remove the instance value of Float::DIG. * Update ext/bigdecimal/bigdecimal.c Fix mis-description of precision * Update ext/bigdecimal/bigdecimal.c Fix the description of precision determination * Update ext/bigdecimal/bigdecimal.c Add the description of the precision in the Rational case. Co-authored-by: Kenta Murata <3959+mrkn@users.noreply.github.com> --- ext/bigdecimal/bigdecimal.c | 265 ++++++++++++++++++++++-------------- 1 file changed, 163 insertions(+), 102 deletions(-) diff --git a/ext/bigdecimal/bigdecimal.c b/ext/bigdecimal/bigdecimal.c index 3be0ffec..5af6c7c5 100644 --- a/ext/bigdecimal/bigdecimal.c +++ b/ext/bigdecimal/bigdecimal.c @@ -256,7 +256,7 @@ GetVpValue(VALUE v, int must) } /* call-seq: - * BigDecimal.double_fig + * BigDecimal.double_fig -> integer * * Returns the number of digits a Float object is allowed to have; * the result is system-dependent: @@ -524,7 +524,7 @@ BigDecimal_n_significant_digits(VALUE self) /* * call-seq: - * hash -> integer + * hash -> integer * * Returns the integer hash value for +self+. * @@ -555,7 +555,7 @@ BigDecimal_hash(VALUE self) /* * call-seq: - * _dump + * _dump -> string * * Returns a string representing the marshalling of +self+. * See module Marshal. @@ -1236,12 +1236,17 @@ BigDecimal_uplus(VALUE self) /* * call-seq: - * self + value -> new_bigdecimal + * self + value -> bigdecimal * - * Returns the sum of +self+ and +value+: + * Returns the \BigDecimal sum of +self+ and +value+: * * b = BigDecimal('111111.111') # => 0.111111111e6 - * b + 1 # => 0.111112111e6 + * b + 2 # => 0.111113111e6 + * b + 2.0 # => 0.111113111e6 + * b + Rational(2, 1) # => 0.111113111e6 + * b + Complex(2, 0) # => (0.111113111e6+0i) + * + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. * */ @@ -1286,21 +1291,18 @@ BigDecimal_add(VALUE self, VALUE r) return VpCheckGetValue(c); } - /* call-seq: - * a - b -> bigdecimal - * - * Subtract the specified value. - * - * e.g. - * c = a - b + /* call-seq: + * self - value -> bigdecimal * - * The precision of the result value depends on the type of +b+. + * Returns the \BigDecimal difference of +self+ and +value+: * - * If +b+ is a Float, the precision of the result is Float::DIG+1. + * b = BigDecimal('333333.333') # => 0.333333333e6 + * b - 2 # => 0.333331333e6 + * b - 2.0 # => 0.333331333e6 + * b - Rational(2, 1) # => 0.333331333e6 + * b - Complex(2, 0) # => (0.333331333e6+0i) * - * If +b+ is a BigDecimal, the precision of the result is +b+'s precision of - * internal representation from platform. So, it's return value is platform - * dependent. + * See the {Note About Precision}[BigDecimal.html#class-BigDecimal-label-A+Note+About+Precision]. * */ static VALUE @@ -1479,12 +1481,19 @@ BigDecimal_eq(VALUE self, VALUE r) return BigDecimalCmp(self, r, '='); } -/* call-seq: - * a < b +/* call-seq: + * self < other -> true or false + * + * Returns +true+ if +self+ is less than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b < 2 # => true + * b < 2.0 # => true + * b < Rational(2, 1) # => true + * b < 1.5 # => false * - * Returns true if a is less than b. + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_lt(VALUE self, VALUE r) @@ -1492,12 +1501,20 @@ BigDecimal_lt(VALUE self, VALUE r) return BigDecimalCmp(self, r, '<'); } -/* call-seq: - * a <= b +/* call-seq: + * self <= other -> true or false + * + * Returns +true+ if +self+ is less or equal to than +other+, +false+ otherwise: + * + * b = BigDecimal('1.5') # => 0.15e1 + * b <= 2 # => true + * b <= 2.0 # => true + * b <= Rational(2, 1) # => true + * b <= 1.5 # => true + * b < 1 # => false * - * Returns true if a is less than or equal to b. + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_le(VALUE self, VALUE r) @@ -1505,12 +1522,19 @@ BigDecimal_le(VALUE self, VALUE r) return BigDecimalCmp(self, r, 'L'); } -/* call-seq: - * a > b +/* call-seq: + * self > other -> true or false + * + * Returns +true+ if +self+ is greater than +other+, +false+ otherwise: * - * Returns true if a is greater than b. + * b = BigDecimal('1.5') + * b > 1 # => true + * b > 1.0 # => true + * b > Rational(1, 1) # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce). */ static VALUE BigDecimal_gt(VALUE self, VALUE r) @@ -1518,12 +1542,20 @@ BigDecimal_gt(VALUE self, VALUE r) return BigDecimalCmp(self, r, '>'); } -/* call-seq: - * a >= b +/* call-seq: + * self >= other -> true or false + * + * Returns +true+ if +self+ is greater than or equal to +other+, +false+ otherwise: * - * Returns true if a is greater than or equal to b. + * b = BigDecimal('1.5') + * b >= 1 # => true + * b >= 1.0 # => true + * b >= Rational(1, 1) # => true + * b >= 1.5 # => true + * b > 2 # => false + * + * Raises an exception if the comparison cannot be made. * - * Values may be coerced to perform the comparison (see ==, BigDecimal#coerce) */ static VALUE BigDecimal_ge(VALUE self, VALUE r) @@ -1533,11 +1565,14 @@ BigDecimal_ge(VALUE self, VALUE r) /* * call-seq: - * -big_decimal -> big_decimal + * -self -> bigdecimal + * + * Returns the \BigDecimal negation of self: * - * Return the negation of self. + * b0 = BigDecimal('1.5') + * b1 = -b0 # => -0.15e1 + * b2 = -b1 # => 0.15e1 * - * -BigDecimal('5') #=> -0.5e1 */ static VALUE @@ -1551,21 +1586,6 @@ BigDecimal_neg(VALUE self) return VpCheckGetValue(c); } - /* - * Document-method: BigDecimal#mult - * - * call-seq: mult(value, digits) - * - * Multiply by the specified value. - * - * e.g. - * c = a.mult(b,n) - * c = a * b - * - * digits:: If specified and less than the number of significant digits of the - * result, the result is rounded to that number of digits, according - * to BigDecimal.mode. - */ static VALUE BigDecimal_mult(VALUE self, VALUE r) { @@ -2032,7 +2052,7 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * call-seq: * add(value, ndigits) -> new_bigdecimal * - * Returns the sum of +self+ and +value+ + * Returns the \BigDecimal sum of +self+ and +value+ * with a precision of +ndigits+ decimal digits. * * When +ndigits+ is less than the number of significant digits @@ -2043,15 +2063,13 @@ BigDecimal_div3(int argc, VALUE *argv, VALUE self) * * # Set the rounding mode. * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) - * BigDecimal('111111.111').add(1, 0) # => 0.111112111e6 - * BigDecimal('111111.111').add(1, 2) # => 0.11e6 - * BigDecimal('111111.111').add(1, 3) # => 0.111e6 - * BigDecimal('111111.111').add(1, 4) # => 0.1111e6 - * BigDecimal('111111.111').add(1, 5) # => 0.11111e6 - * BigDecimal('111111.111').add(1, 6) # => 0.111112e6 - * BigDecimal('111111.111').add(1, 7) # => 0.1111121e6 - * BigDecimal('111111.111').add(1, 8) # => 0.11111211e6 - * BigDecimal('111111.111').add(1, 9) # => 0.111112111e6 + * b = BigDecimal('111111.111') + * b.add(1, 0) # => 0.111112111e6 + * b.add(1, 3) # => 0.111e6 + * b.add(1, 6) # => 0.111112e6 + * b.add(1, 15) # => 0.111112111e6 + * b.add(1.0, 15) # => 0.111112111e6 + * b.add(Rational(1, 1), 15) # => 0.111112111e6 * */ @@ -2102,6 +2120,31 @@ BigDecimal_sub2(VALUE self, VALUE b, VALUE n) } } + /* + * call-seq: + * mult(other, ndigits) -> bigdecimal + * + * Returns the \BigDecimal product of +self+ and +value+ + * with a precision of +ndigits+ decimal digits. + * + * When +ndigits+ is less than the number of significant digits + * in the sum, the sum is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. + * + * Examples: + * + * # Set the rounding mode. + * BigDecimal.mode(BigDecimal::ROUND_MODE, :half_up) + * b = BigDecimal('555555.555') + * b.mult(3, 0) # => 0.1666666665e7 + * b.mult(3, 3) # => 0.167e7 + * b.mult(3, 6) # => 0.166667e7 + * b.mult(3, 15) # => 0.1666666665e7 + * b.mult(3.0, 0) # => 0.1666666665e7 + * b.mult(Rational(3, 1), 0) # => 0.1666666665e7 + * b.mult(Complex(3, 0), 0) # => (0.1666666665e7+0.0i) + * + */ static VALUE BigDecimal_mult2(VALUE self, VALUE b, VALUE n) @@ -2122,12 +2165,13 @@ BigDecimal_mult2(VALUE self, VALUE b, VALUE n) /* * call-seq: - * big_decimal.abs -> big_decimal + * abs -> bigdecimal * - * Returns the absolute value, as a BigDecimal. + * Returns the \BigDecimal absolute value of +self+: + * + * BigDecimal('5').abs # => 0.5e1 + * BigDecimal('-3').abs # => 0.3e1 * - * BigDecimal('5').abs #=> 0.5e1 - * BigDecimal('-3').abs #=> 0.3e1 */ static VALUE @@ -2973,12 +3017,18 @@ BigDecimal_power(int argc, VALUE*argv, VALUE self) return VpCheckGetValue(y); } -/* call-seq: - * a ** n -> bigdecimal +/* call-seq: + * self ** other -> bigdecimal * - * Returns the value raised to the power of n. + * Returns the \BigDecimal value of +self+ raised to power +other+: + * + * b = BigDecimal('3.14') + * b ** 2 # => 0.98596e1 + * b ** 2.0 # => 0.98596e1 + * b ** Rational(2, 1) # => 0.98596e1 + * + * Related: BigDecimal#power. * - * See BigDecimal#power. */ static VALUE BigDecimal_power_op(VALUE self, VALUE exp) @@ -3426,50 +3476,49 @@ rb_convert_to_BigDecimal(VALUE val, size_t digs, int raise_exception) return rb_str_convert_to_BigDecimal(str, digs, raise_exception); } -/* call-seq: - * BigDecimal(arg, exception: true) - * BigDecimal(arg, digits, exception: true) - * - * Returns arg converted to a BigDecimal. Numeric types are converted - * directly. Other types except for String are first converted to String - * by to_str. Strings can be converted when it has appropriate - * forms of decimal numbers. Exceptions can be suppressed by passing - * exception: false. +/* call-seq: + * BigDecimal(value, exception: true) -> bigdecimal + * BigDecimal(value, ndigits, exception: true) -> bigdecimal * - * When arg is a Float and digits is 0, the number - * of digits is determined by the algorithm of dtoa function - * written by David M. Gay. That algorithm is based on "How to Print Floating- - * Point Numbers Accurately" by Guy L. Steele, Jr. and Jon L. White [Proc. ACM - * SIGPLAN '90, pp. 112-126]. + * Returns the \BigDecimal converted from +value+ + * with a precision of +ndigits+ decimal digits. * - * arg:: The value converted to a BigDecimal. + * When +ndigits+ is less than the number of significant digits + * in the value, the result is rounded to that number of digits, + * according to the current rounding mode; see BigDecimal.mode. * - * If it is a String, spaces are ignored and unrecognized characters - * terminate the value. + * Returns +value+ converted to a \BigDecimal, depending on the type of +value+: * - * digits:: The number of significant digits, as an Integer. If omitted, - * the number of significant digits is determined from arg. + * - Integer, Float, Rational, Complex, or BigDecimal: converted directly: * - * The actual number of significant digits used in computation is - * usually larger than the specified number. + * # Integer, Complex, or BigDecimal value does not require ndigits; ignored if given. + * BigDecimal(2) # => 0.2e1 + * BigDecimal(Complex(2, 0)) # => 0.2e1 + * BigDecimal(BigDecimal(2)) # => 0.2e1 + * # Float or Rational value requires ndigits. + * BigDecimal(2.0, 0) # => 0.2e1 + * BigDecimal(Rational(2, 1), 0) # => 0.2e1 * - * exception:: Whether an exception should be raised on invalid arguments. - * +true+ by default, if passed +false+, just returns +nil+ - * for invalid. + * - String: converted by parsing if it contains an integer or floating-point literal; + * leading and trailing whitespace is ignored: * + * # String does not require ndigits; ignored if given. + * BigDecimal('2') # => 0.2e1 + * BigDecimal('2.0') # => 0.2e1 + * BigDecimal('0.2e1') # => 0.2e1 + * BigDecimal(' 2.0 ') # => 0.2e1 * - * ==== Exceptions + * - Other type that responds to method :to_str: + * first converted to a string, then converted to a \BigDecimal, as above. * - * TypeError:: If the +initial+ type is neither Integer, Float, - * Rational, nor BigDecimal, this exception is raised. + * - Other type: * - * TypeError:: If the +digits+ is not an Integer, this exception is raised. + * - Raises an exception if keyword argument +exception+ is +true+. + * - Returns +nil+ if keyword argument +exception+ is +true+. * - * ArgumentError:: If +initial+ is a Float, and the +digits+ is larger than - * Float::DIG + 1, this exception is raised. + * Raises an exception if +value+ evaluates to a Float + * and +digits+ is larger than Float::DIG + 1. * - * ArgumentError:: If the +initial+ is a Float or Rational, and the +digits+ - * value is omitted, this exception is raised. */ static VALUE f_BigDecimal(int argc, VALUE *argv, VALUE self) @@ -4019,6 +4068,18 @@ BigDecimal_negative_zero(void) * * (1.2 - 1.0) == 0.2 #=> false * + * == A Note About Precision + * + * For a calculation using a \BigDecimal and another +value+, + * the precision of the result depends on the type of +value+: + * + * - If +value+ is a \Float, + * the precision is Float::DIG + 1. + * - If +value+ is a \Rational, the precision is larger than Float::DIG + 1. + * - If +value+ is a \BigDecimal, the precision is +value+'s precision in the + * internal representation, which is platform-dependent. + * - If +value+ is other object, the precision is determined by the result of +BigDecimal(value)+. + * * == Special features of accurate decimal arithmetic * * Because BigDecimal is more accurate than normal binary floating point From 5c29b730f508fb88311a2456b960a4f2feb68bef Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Mon, 3 May 2021 15:52:18 +0900 Subject: [PATCH 142/144] Add 3.0.2 entry --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5132d6e3..815e655a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,15 @@ # CHANGES +## 3.0.2 + +*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* + +* Revert the changes in 3.0.1 due to remaining bugs. + ## 3.0.1 +*This version is yanked due to the remaining bugs.* + * Improve the conversion speed of BigDecimal() and to_d methods. **Kenta Murata** From 84f4d3bb903e259d2032579e2d1af91c1f0b0b6c Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Dec 2021 18:40:20 +0900 Subject: [PATCH 143/144] CHANGES: Add 3.1.0 entries --- CHANGES.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 815e655a..209572c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,29 +1,71 @@ # CHANGES -## 3.0.2 +## 3.1.0 -*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* +* Improve documentation [GH-209] -* Revert the changes in 3.0.1 due to remaining bugs. + **Burdette Lamar** -## 3.0.1 +* Let BigDecimal#quo accept precision. [GH-214] [Bug #8826] -*This version is yanked due to the remaining bugs.* + Reported by Földes László + +* Allow passing both float and precision in BigDecimal#div. [GH-212] [Bug #8826] + + Reported by Földes László + +* Add `BigDecimal#scale` and `BigDecimal#precision_scale` + + **Kenta Murata** + +* Fix a bug of `BigDecimal#precision` for the case that a BigDecimal has single internal digit [GH-205] + + **Kenta Murata** + +* Fix segmentation fault due to a bug of `VpReallocReal` + + **Kenta Murata** + +* Use larger precision in divide for irrational or recurring results. [GH-94] [Bug #13754] -* Improve the conversion speed of BigDecimal() and to_d methods. + Reported by Lionel PERRIN + +* Fix negative Bignum conversion [GH-196] + + **Jean byroot Boussier** + +* Fix grammar in error messages. [GH-196] + + **Olle Jonsson** + +* Improve the conversion speed of `Kernel#BigDecimal` and `to_d` methods. **Kenta Murata** -* Permit 0 digits in BigDecimal(float) and Float#to_d. +* Fix trailing zeros handling in `rb_uint64_convert_to_BigDecimal`. [GH-192] + + Reported by @kamipo + +* Permit 0 digits in `BigDecimal(float)` and `Float#to_d`. It means auto-detection of the smallest number of digits to represent - the given Float number without error. + the given Float number without error. [GH-180] **Kenta Murata** -* Fix precision issue of Float [GH-70] +* Fix precision issue of Float. [GH-70] [Bug #13331] Reported by @casperisfine +## 3.0.2 + +*This version is totally same as 3.0.0. This was released for reverting 3.0.1.* + +* Revert the changes in 3.0.1 due to remaining bugs. + +## 3.0.1 + +*This version is yanked due to the remaining bugs.* + ## 3.0.0 * Deprecate `BigDecimal#precs`. From c3453d2b97dc39e3084ecad29552861acbe3ef21 Mon Sep 17 00:00:00 2001 From: Kenta Murata Date: Tue, 21 Dec 2021 18:40:45 +0900 Subject: [PATCH 144/144] Version 3.1.0 --- bigdecimal.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigdecimal.gemspec b/bigdecimal.gemspec index 1d8b36ee..2b1ff6a6 100644 --- a/bigdecimal.gemspec +++ b/bigdecimal.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "bigdecimal" - s.version = "3.1.0.dev" + s.version = "3.1.0" s.authors = ["Kenta Murata", "Zachary Scott", "Shigeo Kobayashi"] s.email = ["mrkn@mrkn.jp"]