├── test ├── lib │ └── helper.rb └── bigdecimal │ ├── test_ractor.rb │ ├── helper.rb │ ├── test_jruby.rb │ ├── test_bigdecimal_util.rb │ ├── test_vp_operation.rb │ └── test_bigmath.rb ├── .github ├── dependabot.yml └── workflows │ ├── jruby_test.yml │ ├── push_gem.yml │ ├── benchmark.yml │ └── ci.yml ├── bin ├── setup └── console ├── .gitignore ├── Gemfile ├── benchmark ├── from_large_integer.yml ├── from_small_integer.yml └── from_float.yml ├── sample ├── pi.rb ├── nlsolve.rb └── linear.rb ├── ext └── bigdecimal │ ├── depend │ ├── missing.c │ ├── static_assert.h │ ├── extconf.rb │ ├── feature.h │ ├── missing.h │ ├── bits.h │ └── bigdecimal.h ├── bigdecimal.gemspec ├── lib ├── bigdecimal │ ├── newton.rb │ ├── jacobian.rb │ ├── ludcmp.rb │ ├── util.rb │ └── math.rb └── bigdecimal.rb ├── Rakefile ├── README.md ├── LICENSE └── CHANGES.md /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.dyld 12 | *.so 13 | *.o 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem "benchmark_driver" 6 | gem "fiddle", platform: :ruby 7 | gem "rake", ">= 12.3.3" 8 | gem "rake-compiler", ">= 0.9" 9 | gem "minitest", "< 5.0.0" 10 | gem "irb" 11 | gem "test-unit" 12 | gem "test-unit-ruby-core" 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "bigdecimal" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | require "irb" 10 | IRB.start 11 | -------------------------------------------------------------------------------- /benchmark/from_large_integer.yml: -------------------------------------------------------------------------------- 1 | loop_count: 1000 2 | 3 | contexts: 4 | - gems: 5 | bigdecimal: 3.1.1 6 | - name: "master" 7 | prelude: |- 8 | $LOAD_PATH.unshift(File.expand_path("lib")) 9 | require "bigdecimal" 10 | 11 | prelude: |- 12 | figs = (0..9).to_a 13 | 14 | int_n10000 = 9999.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } 15 | 16 | benchmark: 17 | int_n10000: BigDecimal(int_n10000) 18 | -------------------------------------------------------------------------------- /sample/pi.rb: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # pi.rb 6 | # 7 | # Calculates 3.1415.... (the number of times that a circle's diameter 8 | # will fit around the circle) using J. Machin's formula. 9 | # 10 | 11 | require "bigdecimal" 12 | require "bigdecimal/math.rb" 13 | 14 | include BigMath 15 | 16 | if ARGV.size == 1 17 | print "PI("+ARGV[0]+"):\n" 18 | p PI(ARGV[0].to_i) 19 | else 20 | print "TRY: ruby pi.rb 1000 \n" 21 | end 22 | -------------------------------------------------------------------------------- /ext/bigdecimal/depend: -------------------------------------------------------------------------------- 1 | Makefile: $(BIGDECIMAL_RB) 2 | 3 | # AUTOGENERATED DEPENDENCIES START 4 | bigdecimal.o: $(RUBY_EXTCONF_H) 5 | bigdecimal.o: $(arch_hdrdir)/ruby/config.h 6 | bigdecimal.o: $(hdrdir)/ruby/defines.h 7 | bigdecimal.o: $(hdrdir)/ruby/intern.h 8 | bigdecimal.o: $(hdrdir)/ruby/missing.h 9 | bigdecimal.o: $(hdrdir)/ruby/ruby.h 10 | bigdecimal.o: $(hdrdir)/ruby/st.h 11 | bigdecimal.o: $(hdrdir)/ruby/subst.h 12 | bigdecimal.o: $(hdrdir)/ruby/util.h 13 | bigdecimal.o: bigdecimal.c 14 | bigdecimal.o: bigdecimal.h 15 | # AUTOGENERATED DEPENDENCIES END 16 | -------------------------------------------------------------------------------- /benchmark/from_small_integer.yml: -------------------------------------------------------------------------------- 1 | loop_count: 100000 2 | 3 | contexts: 4 | - gems: 5 | bigdecimal: 3.1.1 6 | - name: "master" 7 | prelude: |- 8 | $LOAD_PATH.unshift(File.expand_path("lib")) 9 | require "bigdecimal" 10 | 11 | prelude: |- 12 | figs = (0..9).to_a 13 | 14 | int_n9 = 8.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } 15 | int_n19 = 18.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } 16 | int_n38 = 37.times.inject(figs[1..-1].sample) {|a, x| a * 10 + x } 17 | 18 | benchmark: 19 | int_n9: BigDecimal(int_n9) 20 | int_n19: BigDecimal(int_n19) 21 | int_n38: BigDecimal(int_n38) 22 | -------------------------------------------------------------------------------- /test/bigdecimal/test_ractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative "helper" 3 | 4 | class TestBigDecimalRactor < Test::Unit::TestCase 5 | include TestBigDecimalBase 6 | 7 | def setup 8 | super 9 | omit unless defined? Ractor 10 | end 11 | 12 | def test_ractor_shareable 13 | assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") 14 | begin; 15 | $VERBOSE = nil 16 | class Ractor 17 | alias value take unless method_defined? :value 18 | end 19 | 20 | require "bigdecimal" 21 | r = Ractor.new BigDecimal(Math::PI, Float::DIG+1) do |pi| 22 | BigDecimal('2.0')*pi 23 | end 24 | assert_equal(2*Math::PI, r.value) 25 | end; 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /.github/workflows/jruby_test.yml: -------------------------------------------------------------------------------- 1 | name: JRuby-test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | host: 15 | name: ${{ matrix.ruby }} 16 | runs-on: ubuntu-latest 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | ruby: 21 | - jruby 22 | - jruby-head 23 | 24 | steps: 25 | - uses: actions/checkout@v6.0.1 26 | 27 | - name: Set up Ruby 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | ruby-version: ${{ matrix.ruby }} 31 | 32 | - run: bundle install 33 | 34 | - run: rake compile 35 | 36 | - run: rake test TEST=test/bigdecimal/test_jruby.rb 37 | 38 | - run: rake build 39 | 40 | - run: gem install pkg/*.gem 41 | -------------------------------------------------------------------------------- /ext/bigdecimal/missing.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef HAVE_RUBY_ATOMIC_H 4 | # include 5 | #endif 6 | 7 | #ifdef RUBY_ATOMIC_PTR_CAS 8 | # define ATOMIC_PTR_CAS(var, old, new) RUBY_ATOMIC_PTR_CAS(var, old, new) 9 | #endif 10 | 11 | #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) 12 | /* GCC warns about unknown sanitizer, which is annoying. */ 13 | # undef NO_SANITIZE 14 | # define NO_SANITIZE(x, y) \ 15 | _Pragma("GCC diagnostic push") \ 16 | _Pragma("GCC diagnostic ignored \"-Wattributes\"") \ 17 | __attribute__((__no_sanitize__(x))) y; \ 18 | _Pragma("GCC diagnostic pop") \ 19 | y 20 | #endif 21 | 22 | #undef strtod 23 | #define strtod BigDecimal_strtod 24 | #undef dtoa 25 | #define dtoa BigDecimal_dtoa 26 | #undef hdtoa 27 | #define hdtoa BigDecimal_hdtoa 28 | #include "missing/dtoa.c" 29 | -------------------------------------------------------------------------------- /benchmark/from_float.yml: -------------------------------------------------------------------------------- 1 | loop_count: 100000 2 | 3 | contexts: 4 | - gems: 5 | bigdecimal: 3.1.1 6 | - name: "master" 7 | prelude: |- 8 | $LOAD_PATH.unshift(File.expand_path("lib")) 9 | require "bigdecimal" 10 | 11 | prelude: |- 12 | flt_e0 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}".to_f 13 | flt_ep10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+10".to_f 14 | flt_ep100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e+100".to_f 15 | flt_em10 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-10".to_f 16 | flt_em100 = "0.#{Float::DIG.times.map { [*1..9].sample }.join("")}e-100".to_f 17 | 18 | benchmark: 19 | flt_e0: BigDecimal(flt_e0, Float::DIG+1) 20 | flt_ep10: BigDecimal(flt_ep10, Float::DIG+1) 21 | flt_ep100: BigDecimal(flt_ep100, Float::DIG+1) 22 | flt_em10: BigDecimal(flt_em10, Float::DIG+1) 23 | flt_em100: BigDecimal(flt_em100, Float::DIG+1) 24 | -------------------------------------------------------------------------------- /sample/nlsolve.rb: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # nlsolve.rb 6 | # An example for solving nonlinear algebraic equation system. 7 | # 8 | 9 | require "bigdecimal" 10 | require "bigdecimal/newton" 11 | include Newton 12 | 13 | class Function # :nodoc: all 14 | def initialize() 15 | @zero = BigDecimal("0.0") 16 | @one = BigDecimal("1.0") 17 | @two = BigDecimal("2.0") 18 | @ten = BigDecimal("10.0") 19 | @eps = BigDecimal("1.0e-16") 20 | end 21 | def zero;@zero;end 22 | def one ;@one ;end 23 | def two ;@two ;end 24 | def ten ;@ten ;end 25 | def eps ;@eps ;end 26 | def values(x) # <= defines functions solved 27 | f = [] 28 | f1 = x[0]*x[0] + x[1]*x[1] - @two # f1 = x**2 + y**2 - 2 => 0 29 | f2 = x[0] - x[1] # f2 = x - y => 0 30 | f <<= f1 31 | f <<= f2 32 | f 33 | end 34 | end 35 | 36 | f = BigDecimal.limit(100) 37 | f = Function.new 38 | x = [f.zero,f.zero] # Initial values 39 | n = nlsolve(f,x) 40 | p x 41 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/bigdecimal' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/bigdecimal 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | strategy: 25 | matrix: 26 | ruby: ["ruby", "jruby"] 27 | 28 | steps: 29 | - name: Harden Runner 30 | uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3 31 | with: 32 | egress-policy: audit 33 | 34 | - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v5.0.1 35 | 36 | - name: Set up Ruby 37 | uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0 38 | with: 39 | bundler-cache: true 40 | ruby-version: ${{ matrix.ruby }} 41 | 42 | - name: Publish to RubyGems 43 | uses: rubygems/release-gem@1c162a739e8b4cb21a676e97b087e8268d8fc40b # v1.1.2 44 | 45 | - name: Create GitHub release 46 | run: | 47 | tag_name="$(git describe --tags --abbrev=0)" 48 | gh release create "${tag_name}" --verify-tag --generate-notes 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | if: matrix.ruby == 'ruby' 52 | -------------------------------------------------------------------------------- /.github/workflows/benchmark.yml: -------------------------------------------------------------------------------- 1 | name: Benchmarking 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | ruby-versions: 15 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 16 | with: 17 | engine: cruby 18 | min_version: 2.7 19 | versions: '["debug"]' 20 | 21 | host: 22 | needs: ruby-versions 23 | name: ${{ matrix.os }} ${{ matrix.ruby }} 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | os: 29 | - ubuntu-latest 30 | - macos-latest 31 | - macos-14 32 | - windows-latest 33 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 34 | include: 35 | - { os: windows-latest , ruby: mingw } 36 | - { os: windows-latest , ruby: mswin } 37 | exclude: 38 | - { os: windows-latest , ruby: debug } 39 | 40 | # These are disabled due to the ambiguity of stringio 41 | - { os: windows-latest , ruby: "3.0" } 42 | - { os: windows-latest , ruby: "3.1" } 43 | - { os: windows-latest , ruby: "3.2" } 44 | 45 | steps: 46 | - uses: actions/checkout@v6.0.1 47 | 48 | - name: Set up Ruby 49 | uses: ruby/setup-ruby@v1 50 | with: 51 | ruby-version: ${{ matrix.ruby }} 52 | 53 | - name: Install dependencies 54 | run: | 55 | bundle install 56 | gem install bigdecimal -v 3.1.1 57 | 58 | - run: rake compile 59 | 60 | - run: rake benchmark 61 | -------------------------------------------------------------------------------- /ext/bigdecimal/static_assert.h: -------------------------------------------------------------------------------- 1 | #ifndef BIGDECIMAL_STATIC_ASSERT_H 2 | #define BIGDECIMAL_STATIC_ASSERT_H 3 | 4 | #include "feature.h" 5 | 6 | #ifdef HAVE_RUBY_INTERNAL_STATIC_ASSERT_H 7 | # include 8 | #endif 9 | 10 | #ifdef RBIMPL_STATIC_ASSERT 11 | # define STATIC_ASSERT RBIMPL_STATIC_ASSERT 12 | #endif 13 | 14 | #ifndef STATIC_ASSERT 15 | # /* The following section is copied from CRuby's static_assert.h */ 16 | 17 | # if defined(__cplusplus) && defined(__cpp_static_assert) 18 | # /* https://isocpp.org/std/standing-documents/sd-6-sg10-feature-test-recommendations */ 19 | # define BIGDECIMAL_STATIC_ASSERT0 static_assert 20 | 21 | # elif defined(__cplusplus) && defined(_MSC_VER) && _MSC_VER >= 1600 22 | # define BIGDECIMAL_STATIC_ASSERT0 static_assert 23 | 24 | # elif defined(__INTEL_CXX11_MODE__) 25 | # define BIGDECIMAL_STATIC_ASSERT0 static_assert 26 | 27 | # elif defined(__cplusplus) && __cplusplus >= 201103L 28 | # define BIGDECIMAL_STATIC_ASSERT0 static_assert 29 | 30 | # elif defined(__cplusplus) && __has_extension(cxx_static_assert) 31 | # define BIGDECIMAL_STATIC_ASSERT0 __extension__ static_assert 32 | 33 | # elif defined(__STDC_VERSION__) && __has_extension(c_static_assert) 34 | # define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert 35 | 36 | # elif defined(__STDC_VERSION__) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) 37 | # define BIGDECIMAL_STATIC_ASSERT0 __extension__ _Static_assert 38 | #endif 39 | 40 | # if defined(__DOXYGEN__) 41 | # define STATIC_ASSERT static_assert 42 | 43 | # elif defined(BIGDECIMAL_STATIC_ASSERT0) 44 | # define STATIC_ASSERT(name, expr) \ 45 | BIGDECIMAL_STATIC_ASSERT0(expr, #name ": " #expr) 46 | 47 | # else 48 | # define STATIC_ASSERT(name, expr) \ 49 | typedef int static_assert_ ## name ## _check[1 - 2 * !(expr)] 50 | # endif 51 | #endif /* STATIC_ASSERT */ 52 | 53 | 54 | #endif /* BIGDECIMAL_STATIC_ASSERT_H */ 55 | -------------------------------------------------------------------------------- /sample/linear.rb: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/ruby 2 | # frozen_string_literal: false 3 | 4 | # 5 | # linear.rb 6 | # 7 | # Solves linear equation system(A*x = b) by LU decomposition method. 8 | # where A is a coefficient matrix,x is an answer vector,b is a constant vector. 9 | # 10 | # USAGE: 11 | # ruby linear.rb [input file solved] 12 | # 13 | 14 | # :stopdoc: 15 | require "bigdecimal" 16 | require "bigdecimal/ludcmp" 17 | 18 | # 19 | # NOTE: 20 | # Change following BigDecimal.limit() if needed. 21 | BigDecimal.limit(100) 22 | # 23 | 24 | include LUSolve 25 | def rd_order(na) 26 | printf("Number of equations ?") if(na <= 0) 27 | n = ARGF.gets().to_i 28 | end 29 | 30 | na = ARGV.size 31 | zero = BigDecimal("0.0") 32 | one = BigDecimal("1.0") 33 | 34 | while (n=rd_order(na))>0 35 | a = [] 36 | as= [] 37 | b = [] 38 | if na <= 0 39 | # Read data from console. 40 | printf("\nEnter coefficient matrix element A[i,j]\n") 41 | for i in 0...n do 42 | for j in 0...n do 43 | printf("A[%d,%d]? ",i,j); s = ARGF.gets 44 | a << BigDecimal(s) 45 | as << BigDecimal(s) 46 | end 47 | printf("Contatant vector element b[%d] ? ",i) 48 | b << BigDecimal(ARGF.gets) 49 | end 50 | else 51 | # Read data from specified file. 52 | printf("Coefficient matrix and constant vector.\n") 53 | for i in 0...n do 54 | s = ARGF.gets 55 | printf("%d) %s",i,s) 56 | s = s.split 57 | for j in 0...n do 58 | a << BigDecimal(s[j]) 59 | as << BigDecimal(s[j]) 60 | end 61 | b << BigDecimal(s[n]) 62 | end 63 | end 64 | x = lusolve(a,b,ludecomp(a,n,zero,one),zero) 65 | printf("Answer(x[i] & (A*x-b)[i]) follows\n") 66 | for i in 0...n do 67 | printf("x[%d]=%s ",i,x[i].to_s) 68 | s = zero 69 | for j in 0...n do 70 | s = s + as[i*n+j]*x[j] 71 | end 72 | printf(" & %s\n",(s-b[i]).to_s) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | ruby-versions: 15 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 16 | with: 17 | engine: cruby-truffleruby 18 | min_version: 2.5 19 | versions: '["debug"]' 20 | 21 | host: 22 | needs: ruby-versions 23 | name: ${{ matrix.os }} ${{ matrix.ruby }} decdig-${{ matrix.decdig_bits }}bit 24 | runs-on: ${{ matrix.os }} 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | os: 29 | - ubuntu-latest 30 | - macos-latest 31 | - macos-14 32 | - windows-latest 33 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 34 | decdig_bits: [32] 35 | include: 36 | - { os: ubuntu-latest, ruby: "3.4", decdig_bits: 16 } 37 | - { os: windows-latest , ruby: mingw } 38 | - { os: windows-latest , ruby: mswin } 39 | exclude: 40 | - { os: macos-latest , ruby: "2.5" } 41 | - { os: macos-14 , ruby: "2.5" } 42 | - { os: windows-latest , ruby: debug } 43 | - { os: windows-latest , ruby: truffleruby } 44 | - { os: windows-latest , ruby: truffleruby-head } 45 | env: 46 | BIGDECIMAL_USE_DECDIG_UINT16_T: ${{ matrix.decdig_bits == 16 }} 47 | BIGDECIMAL_USE_VP_TEST_METHODS: true 48 | 49 | steps: 50 | - uses: actions/checkout@v6.0.1 51 | 52 | - name: Set up Ruby 53 | uses: ruby/setup-ruby@v1 54 | with: 55 | ruby-version: ${{ matrix.ruby }} 56 | 57 | - run: bundle install 58 | 59 | - run: rake compile 60 | 61 | - run: rake test 62 | 63 | - run: rake build 64 | 65 | - run: gem install pkg/*.gem 66 | if: ${{ matrix.ruby != 'debug' && ( matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' ) }} 67 | -------------------------------------------------------------------------------- /ext/bigdecimal/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'mkmf' 3 | 4 | def have_builtin_func(name, check_expr, opt = "", &b) 5 | checking_for checking_message(name.funcall_style, nil, opt) do 6 | if try_compile(<= 2.5.0") 55 | 56 | s.metadata["changelog_uri"] = s.homepage + "/blob/master/CHANGES.md" 57 | end 58 | -------------------------------------------------------------------------------- /lib/bigdecimal/newton.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require "bigdecimal/ludcmp" 3 | require "bigdecimal/jacobian" 4 | 5 | # 6 | # newton.rb 7 | # 8 | # Solves the nonlinear algebraic equation system f = 0 by Newton's method. 9 | # This program is not dependent on BigDecimal. 10 | # 11 | # To call: 12 | # n = nlsolve(f,x) 13 | # where n is the number of iterations required, 14 | # x is the initial value vector 15 | # f is an Object which is used to compute the values of the equations to be solved. 16 | # It must provide the following methods: 17 | # 18 | # f.values(x):: returns the values of all functions at x 19 | # 20 | # f.zero:: returns 0.0 21 | # f.one:: returns 1.0 22 | # f.two:: returns 2.0 23 | # f.ten:: returns 10.0 24 | # 25 | # f.eps:: returns the convergence criterion (epsilon value) used to determine whether two values are considered equal. If |a-b| < epsilon, the two values are considered equal. 26 | # 27 | # On exit, x is the solution vector. 28 | # 29 | module Newton 30 | include LUSolve 31 | include Jacobian 32 | module_function 33 | 34 | def norm(fv,zero=0.0) # :nodoc: 35 | s = zero 36 | n = fv.size 37 | for i in 0...n do 38 | s += fv[i]*fv[i] 39 | end 40 | s 41 | end 42 | 43 | # See also Newton 44 | def nlsolve(f,x) 45 | nRetry = 0 46 | n = x.size 47 | 48 | f0 = f.values(x) 49 | zero = f.zero 50 | one = f.one 51 | two = f.two 52 | p5 = one/two 53 | d = norm(f0,zero) 54 | minfact = f.ten*f.ten*f.ten 55 | minfact = one/minfact 56 | e = f.eps 57 | while d >= e do 58 | nRetry += 1 59 | # Not yet converged. => Compute Jacobian matrix 60 | dfdx = jacobian(f,f0,x) 61 | # Solve dfdx*dx = -f0 to estimate dx 62 | dx = lusolve(dfdx,f0,ludecomp(dfdx,n,zero,one),zero) 63 | fact = two 64 | xs = x.dup 65 | begin 66 | fact *= p5 67 | if fact < minfact then 68 | raise "Failed to reduce function values." 69 | end 70 | for i in 0...n do 71 | x[i] = xs[i] - dx[i]*fact 72 | end 73 | f0 = f.values(x) 74 | dn = norm(f0,zero) 75 | end while(dn>=d) 76 | d = dn 77 | end 78 | nRetry 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler::GemHelper.install_tasks 3 | 4 | require "rake" 5 | require "rake/extensiontask" 6 | require "rake/testtask" 7 | 8 | if RUBY_ENGINE == 'jruby' 9 | # JRuby's extension is included with JRuby currently 10 | task :compile do; end 11 | else 12 | Rake::ExtensionTask.new('bigdecimal', Bundler::GemHelper.gemspec) 13 | end 14 | 15 | Rake::TestTask.new do |t| 16 | t.libs << 'test/lib' 17 | t.ruby_opts << '-rhelper' 18 | t.test_files = FileList['test/bigdecimal/**/test_*.rb'] 19 | t.warning = true 20 | end 21 | 22 | task travis: :test 23 | task test: :compile 24 | 25 | benchmark_tasks = [] 26 | namespace :benchmark do 27 | Dir.glob("benchmark/*.yml") do |benchmark| 28 | name = File.basename(benchmark, ".*") 29 | env = { 30 | "RUBYLIB" => nil, 31 | "BUNDLER_ORIG_RUBYLIB" => nil, 32 | } 33 | command_line = [ 34 | RbConfig.ruby, "-v", "-S", "benchmark-driver", File.expand_path(benchmark) 35 | ] 36 | 37 | desc "Run #{name} benchmark" 38 | task name do 39 | puts("```") 40 | sh(env, *command_line) 41 | puts("```") 42 | end 43 | benchmark_tasks << "benchmark:#{name}" 44 | end 45 | end 46 | 47 | desc "Run all benchmarks" 48 | task benchmark: benchmark_tasks 49 | 50 | def bump_version(version, commit: false) 51 | bigdecimal_c = File.read("ext/bigdecimal/bigdecimal.c") 52 | current_version = bigdecimal_c[/^#define BIGDECIMAL_VERSION "(.*)"/, 1] 53 | version = version || current_version.succ 54 | puts "Bumping version from #{current_version} to #{version}" 55 | bigdecimal_c.gsub!(/^#define BIGDECIMAL_VERSION "(.*)"/, "#define BIGDECIMAL_VERSION \"#{version}\"") 56 | File.write("ext/bigdecimal/bigdecimal.c", bigdecimal_c) 57 | 58 | if commit 59 | puts "Committing changes" 60 | sh("git", "add", "ext/bigdecimal/bigdecimal.c") 61 | sh("git", "commit", "-m", "Bump version to #{version}") 62 | else 63 | puts "Changes are not committed" 64 | end 65 | end 66 | 67 | namespace :dev do 68 | namespace :version do 69 | task :bump, [:version] do |t, args| 70 | bump_version(args[:version], commit: false) 71 | end 72 | 73 | namespace :bump do 74 | task :commit, [:version] do |t, args| 75 | bump_version(args[:version], commit: true) 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /ext/bigdecimal/feature.h: -------------------------------------------------------------------------------- 1 | #ifndef BIGDECIMAL_HAS_FEATURE_H 2 | #define BIGDECIMAL_HAS_FEATURE_H 3 | 4 | /* ======== __has_feature ======== */ 5 | 6 | #ifndef __has_feature 7 | # define __has_feature(_) 0 8 | #endif 9 | 10 | /* ======== __has_extension ======== */ 11 | 12 | #ifndef __has_extension 13 | # define __has_extension __has_feature 14 | #endif 15 | 16 | /* ======== __has_builtin ======== */ 17 | 18 | #ifdef HAVE_RUBY_INTERNAL_HAS_BUILTIN_H 19 | # include 20 | #endif 21 | 22 | #ifdef RBIMPL_HAS_BUILTIN 23 | # define BIGDECIMAL_HAS_BUILTIN(...) RBIMPL_HAS_BUILTIN(__VA_ARGS__) 24 | 25 | #else 26 | # /* The following section is copied from CRuby's builtin.h */ 27 | # 28 | # ifdef __has_builtin 29 | # if defined(__INTEL_COMPILER) 30 | # /* :TODO: Intel C Compiler has __has_builtin (since 19.1 maybe?), and is 31 | # * reportedly broken. We have to skip them. However the situation can 32 | # * change. They might improve someday. We need to revisit here later. */ 33 | # elif defined(__GNUC__) && ! __has_builtin(__builtin_alloca) 34 | # /* FreeBSD's defines its own *broken* version of 35 | # * __has_builtin. Cygwin copied that content to be a victim of the 36 | # * broken-ness. We don't take them into account. */ 37 | # else 38 | # define HAVE___HAS_BUILTIN 1 39 | # endif 40 | # endif 41 | # 42 | # if defined(HAVE___HAS_BUILTIN) 43 | # define BIGDECIMAL_HAS_BUILTIN(_) __has_builtin(_) 44 | # 45 | # elif defined(__GNUC__) 46 | # define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ 47 | # if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 6)) 48 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clz 1 49 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 1 50 | # else 51 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clz 0 52 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clzl 0 53 | # endif 54 | # elif defined(_MSC_VER) 55 | # define BIGDECIMAL_HAS_BUILTIN(_) 0 56 | # 57 | # else 58 | # define BIGDECIMAL_HAS_BUILTIN(_) BIGDECIMAL_HAS_BUILTIN_ ## _ 59 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clz HAVE_BUILTIN___BUILTIN_CLZ 60 | # define BIGDECIMAL_HAS_BUILTIN___builtin_clzl HAVE_BUILTIN___BUILTIN_CLZL 61 | # endif 62 | #endif /* RBIMPL_HAS_BUILTIN */ 63 | 64 | #ifndef __has_builtin 65 | # define __has_builtin(...) BIGDECIMAL_HAS_BUILTIN(__VA_ARGS__) 66 | #endif 67 | 68 | #endif /* BIGDECIMAL_HAS_FEATURE_H */ 69 | -------------------------------------------------------------------------------- /lib/bigdecimal/jacobian.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | 3 | require 'bigdecimal' 4 | 5 | # require 'bigdecimal/jacobian' 6 | # 7 | # Provides methods to compute the Jacobian matrix of a set of equations at a 8 | # point x. In the methods below: 9 | # 10 | # f is an Object which is used to compute the Jacobian matrix of the equations. 11 | # It must provide the following methods: 12 | # 13 | # f.values(x):: returns the values of all functions at x 14 | # 15 | # f.zero:: returns 0.0 16 | # f.one:: returns 1.0 17 | # f.two:: returns 2.0 18 | # f.ten:: returns 10.0 19 | # 20 | # f.eps:: returns the convergence criterion (epsilon value) used to determine whether two values are considered equal. If |a-b| < epsilon, the two values are considered equal. 21 | # 22 | # x is the point at which to compute the Jacobian. 23 | # 24 | # fx is f.values(x). 25 | # 26 | module Jacobian 27 | module_function 28 | 29 | # Determines the equality of two numbers by comparing to zero, or using the epsilon value 30 | def isEqual(a,b,zero=0.0,e=1.0e-8) 31 | aa = a.abs 32 | bb = b.abs 33 | if aa == zero && bb == zero then 34 | true 35 | else 36 | if ((a-b)/(aa+bb)).abs < e then 37 | true 38 | else 39 | false 40 | end 41 | end 42 | end 43 | 44 | 45 | # Computes the derivative of +f[i]+ at +x[i]+. 46 | # +fx+ is the value of +f+ at +x+. 47 | def dfdxi(f,fx,x,i) 48 | nRetry = 0 49 | n = x.size 50 | xSave = x[i] 51 | ok = 0 52 | ratio = f.ten*f.ten*f.ten 53 | dx = x[i].abs/ratio 54 | dx = fx[i].abs/ratio if isEqual(dx,f.zero,f.zero,f.eps) 55 | dx = f.one/f.ten if isEqual(dx,f.zero,f.zero,f.eps) 56 | until ok>0 do 57 | deriv = [] 58 | nRetry += 1 59 | if nRetry > 100 60 | raise "Singular Jacobian matrix. No change at x[" + i.to_s + "]" 61 | end 62 | dx = dx*f.two 63 | x[i] += dx 64 | fxNew = f.values(x) 65 | for j in 0...n do 66 | if !isEqual(fxNew[j],fx[j],f.zero,f.eps) then 67 | ok += 1 68 | deriv <<= (fxNew[j]-fx[j])/dx 69 | else 70 | deriv <<= f.zero 71 | end 72 | end 73 | x[i] = xSave 74 | end 75 | deriv 76 | end 77 | 78 | # Computes the Jacobian of +f+ at +x+. +fx+ is the value of +f+ at +x+. 79 | def jacobian(f,fx,x) 80 | n = x.size 81 | dfdx = Array.new(n*n) 82 | for i in 0...n do 83 | df = dfdxi(f,fx,x,i) 84 | for j in 0...n do 85 | dfdx[j*n+i] = df[j] 86 | end 87 | end 88 | dfdx 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/bigdecimal/ludcmp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'bigdecimal' 3 | 4 | # 5 | # Solves a*x = b for x, using LU decomposition. 6 | # 7 | module LUSolve 8 | module_function 9 | 10 | # Performs LU decomposition of the n by n matrix a. 11 | def ludecomp(a,n,zero=0,one=1) 12 | prec = BigDecimal.limit(nil) 13 | ps = [] 14 | scales = [] 15 | for i in 0...n do # pick up largest(abs. val.) element in each row. 16 | ps <<= i 17 | nrmrow = zero 18 | ixn = i*n 19 | for j in 0...n do 20 | biggst = a[ixn+j].abs 21 | nrmrow = biggst if biggst>nrmrow 22 | end 23 | if nrmrow>zero then 24 | scales <<= one.div(nrmrow,prec) 25 | else 26 | raise "Singular matrix" 27 | end 28 | end 29 | n1 = n - 1 30 | for k in 0...n1 do # Gaussian elimination with partial pivoting. 31 | biggst = zero; 32 | for i in k...n do 33 | size = a[ps[i]*n+k].abs*scales[ps[i]] 34 | if size>biggst then 35 | biggst = size 36 | pividx = i 37 | end 38 | end 39 | raise "Singular matrix" if biggst<=zero 40 | if pividx!=k then 41 | j = ps[k] 42 | ps[k] = ps[pividx] 43 | ps[pividx] = j 44 | end 45 | pivot = a[ps[k]*n+k] 46 | for i in (k+1)...n do 47 | psin = ps[i]*n 48 | a[psin+k] = mult = a[psin+k].div(pivot,prec) 49 | if mult!=zero then 50 | pskn = ps[k]*n 51 | for j in (k+1)...n do 52 | a[psin+j] -= mult.mult(a[pskn+j],prec) 53 | end 54 | end 55 | end 56 | end 57 | raise "Singular matrix" if a[ps[n1]*n+n1] == zero 58 | ps 59 | end 60 | 61 | # Solves a*x = b for x, using LU decomposition. 62 | # 63 | # a is a matrix, b is a constant vector, x is the solution vector. 64 | # 65 | # ps is the pivot, a vector which indicates the permutation of rows performed 66 | # during LU decomposition. 67 | def lusolve(a,b,ps,zero=0.0) 68 | prec = BigDecimal.limit(nil) 69 | n = ps.size 70 | x = [] 71 | for i in 0...n do 72 | dot = zero 73 | psin = ps[i]*n 74 | for j in 0...i do 75 | dot = a[psin+j].mult(x[j],prec) + dot 76 | end 77 | x <<= b[ps[i]] - dot 78 | end 79 | (n-1).downto(0) do |i| 80 | dot = zero 81 | psin = ps[i]*n 82 | for j in (i+1)...n do 83 | dot = a[psin+j].mult(x[j],prec) + dot 84 | end 85 | x[i] = (x[i]-dot).div(a[psin+i],prec) 86 | end 87 | x 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /ext/bigdecimal/missing.h: -------------------------------------------------------------------------------- 1 | #ifndef MISSING_H 2 | #define MISSING_H 1 3 | 4 | #if defined(__cplusplus) 5 | extern "C" { 6 | #if 0 7 | } /* satisfy cc-mode */ 8 | #endif 9 | #endif 10 | 11 | #ifndef RB_UNUSED_VAR 12 | # if defined(_MSC_VER) && _MSC_VER >= 1911 13 | # define RB_UNUSED_VAR(x) x [[maybe_unused]] 14 | 15 | # elif defined(__has_cpp_attribute) && __has_cpp_attribute(maybe_unused) 16 | # define RB_UNUSED_VAR(x) x [[maybe_unused]] 17 | 18 | # elif defined(__has_c_attribute) && __has_c_attribute(maybe_unused) 19 | # define RB_UNUSED_VAR(x) x [[maybe_unused]] 20 | 21 | # elif defined(__GNUC__) 22 | # define RB_UNUSED_VAR(x) x __attribute__ ((unused)) 23 | 24 | # else 25 | # define RB_UNUSED_VAR(x) x 26 | # endif 27 | #endif /* RB_UNUSED_VAR */ 28 | 29 | #if defined(_MSC_VER) && _MSC_VER >= 1310 30 | # define HAVE___ASSUME 1 31 | 32 | #elif defined(__INTEL_COMPILER) && __INTEL_COMPILER >= 1300 33 | # define HAVE___ASSUME 1 34 | #endif 35 | 36 | #ifndef UNREACHABLE 37 | # if __has_builtin(__builtin_unreachable) 38 | # define UNREACHABLE __builtin_unreachable() 39 | 40 | # elif defined(HAVE___ASSUME) 41 | # define UNREACHABLE __assume(0) 42 | 43 | # else 44 | # define UNREACHABLE /* unreachable */ 45 | # endif 46 | #endif /* UNREACHABLE */ 47 | 48 | /* bool */ 49 | 50 | #ifndef __bool_true_false_are_defined 51 | # include 52 | #endif 53 | 54 | /* dtoa */ 55 | char *BigDecimal_dtoa(double d_, int mode, int ndigits, int *decpt, int *sign, char **rve); 56 | 57 | /* complex */ 58 | 59 | #ifndef HAVE_RB_COMPLEX_REAL 60 | static inline VALUE 61 | rb_complex_real(VALUE cmp) 62 | { 63 | #ifdef RCOMPLEX 64 | return RCOMPLEX(cmp)->real; 65 | #else 66 | return rb_funcall(cmp, rb_intern("real"), 0); 67 | #endif 68 | } 69 | #endif 70 | 71 | #ifndef HAVE_RB_COMPLEX_IMAG 72 | static inline VALUE 73 | rb_complex_imag(VALUE cmp) 74 | { 75 | # ifdef RCOMPLEX 76 | return RCOMPLEX(cmp)->imag; 77 | # else 78 | return rb_funcall(cmp, rb_intern("imag"), 0); 79 | # endif 80 | } 81 | #endif 82 | 83 | /* st */ 84 | 85 | #ifndef ST2FIX 86 | # undef RB_ST2FIX 87 | # define RB_ST2FIX(h) LONG2FIX((long)(h)) 88 | # define ST2FIX(h) RB_ST2FIX(h) 89 | #endif 90 | 91 | /* warning */ 92 | 93 | #if !defined(HAVE_RB_CATEGORY_WARN) || !defined(HAVE_CONST_RB_WARN_CATEGORY_DEPRECATED) 94 | # define rb_category_warn(category, ...) rb_warn(__VA_ARGS__) 95 | #endif 96 | 97 | #if defined(__cplusplus) 98 | #if 0 99 | { /* satisfy cc-mode */ 100 | #endif 101 | } /* extern "C" { */ 102 | #endif 103 | 104 | #endif /* MISSING_H */ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BigDecimal 2 | 3 | [![CI](https://github.com/ruby/bigdecimal/actions/workflows/ci.yml/badge.svg?event=push)](https://github.com/ruby/bigdecimal/actions/workflows/ci.yml) 4 | 5 | BigDecimal provides an arbitrary-precision decimal floating-point number class. 6 | 7 | ## Installation 8 | 9 | Add this line to your application's Gemfile: 10 | 11 | ```ruby 12 | gem 'bigdecimal' 13 | ``` 14 | 15 | And then execute: 16 | 17 | ```bash 18 | bundle 19 | ``` 20 | 21 | Or install it yourself as: 22 | 23 | ```bash 24 | gem install bigdecimal 25 | ``` 26 | 27 | ### For RubyInstaller users 28 | 29 | If your Ruby comes from [RubyInstaller](https://rubyinstaller.org/), make sure [Devkit](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit) is available on your environment before installing bigdecimal. 30 | 31 | ### For Chocolatey 32 | 33 | I don't have enough knowledge about Chocolatey. Please tell me what should I write here. 34 | 35 | ## Which version should you select 36 | 37 | The differences among versions are given below: 38 | 39 | | version | characteristics | Supported ruby version range | 40 | | ------- | --------------- | ----------------------- | 41 | | 3.0.0 | You can use BigDecimal with Ractor on Ruby 3.0 | 2.5 .. | 42 | | 2.0.x | You cannot use BigDecimal.new and do subclassing | 2.4 .. | 43 | | 1.4.x | BigDecimal.new and subclassing always prints warning. | 2.3 .. 2.7 | 44 | | 1.3.5 | You can use BigDecimal.new and subclassing without warning | .. 2.5 | 45 | 46 | You can select the version you want to use using `gem` method in Gemfile or scripts. 47 | For example, you want to stick bigdecimal version 1.3.5, it works file to put the following `gem` call in you Gemfile. 48 | 49 | ```ruby 50 | gem 'bigdecimal', '1.3.5' 51 | ``` 52 | 53 | ## Usage 54 | 55 | TODO: Write usage instructions here 56 | 57 | ## Development 58 | 59 | After checking out the repo, run `bin/setup` to install dependencies. 60 | Then, run `rake test` to run the tests. 61 | You can also run `bin/console` for an interactive prompt that 62 | will allow you to experiment. 63 | 64 | To install this gem onto your local machine, run `bundle exec rake install`. 65 | To release a new version, update the version number in `version.rb`, 66 | and then run `bundle exec rake release`, 67 | which will create a git tag for the version, push git commits and tags, 68 | and push the `.gem` file to [rubygems.org](https://rubygems.org). 69 | 70 | ## Contributing 71 | 72 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/bigdecimal. 73 | 74 | ## License 75 | 76 | BigDecimal is released under the Ruby and 2-clause BSD licenses. 77 | See LICENSE.txt for details. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a) place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b) use the modified software only within your corporation or 18 | organization. 19 | 20 | c) give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d) make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a) distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b) accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c) give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d) make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /test/bigdecimal/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require "test/unit" 3 | require "bigdecimal" 4 | require 'rbconfig/sizeof' 5 | 6 | module TestBigDecimalBase 7 | BASE = BigDecimal::BASE 8 | case BASE 9 | when 1000000000 10 | SIZEOF_DECDIG = RbConfig::SIZEOF["int32_t"] 11 | BASE_FIG = 9 12 | when 10000 13 | SIZEOF_DECDIG = RbConfig::SIZEOF["int16_t"] 14 | BASE_FIG = 4 15 | end 16 | 17 | def setup 18 | @mode = BigDecimal.mode(BigDecimal::EXCEPTION_ALL) 19 | BigDecimal.mode(BigDecimal::EXCEPTION_ALL, true) 20 | BigDecimal.mode(BigDecimal::EXCEPTION_UNDERFLOW, true) 21 | BigDecimal.mode(BigDecimal::EXCEPTION_OVERFLOW, true) 22 | BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_UP) 23 | BigDecimal.limit(0) 24 | end 25 | 26 | def teardown 27 | [BigDecimal::EXCEPTION_INFINITY, BigDecimal::EXCEPTION_NaN, 28 | BigDecimal::EXCEPTION_UNDERFLOW, BigDecimal::EXCEPTION_OVERFLOW].each do |mode| 29 | BigDecimal.mode(mode, !(@mode & mode).zero?) 30 | end 31 | end 32 | 33 | def under_gc_stress 34 | stress, GC.stress = GC.stress, true 35 | yield 36 | ensure 37 | GC.stress = stress 38 | end 39 | 40 | # Asserts that +actual+ is calculated with exactly the given +precision+. 41 | # No extra digits are allowed. Only the last digit may differ at most by one. 42 | def assert_in_exact_precision(expected, actual, precision) 43 | expected = BigDecimal(expected) 44 | delta = BigDecimal(1)._decimal_shift(expected.exponent - precision) 45 | assert actual.n_significant_digits <= precision, "Too many significant digits: #{actual.n_significant_digits} > #{precision}" 46 | assert_in_delta(expected.mult(1, precision), actual, delta) 47 | end 48 | 49 | # Asserts that the calculation of the given block converges to some value 50 | # with exactly the given +precision+. 51 | def assert_converge_in_precision(&block) 52 | expected = yield(200) 53 | [50, 100, 150].each do |n| 54 | value = yield(n) 55 | assert(value != expected, "Unable to estimate precision for exact value") 56 | assert_equal(expected.mult(1, n), value) 57 | end 58 | end 59 | 60 | 61 | def assert_nan(x) 62 | assert(x.nan?, "Expected #{x.inspect} to be NaN") 63 | end 64 | 65 | def assert_positive_infinite(x) 66 | assert(x.infinite?, "Expected #{x.inspect} to be positive infinite") 67 | assert_operator(x, :>, 0) 68 | end 69 | 70 | def assert_negative_infinite(x) 71 | assert(x.infinite?, "Expected #{x.inspect} to be negative infinite") 72 | assert_operator(x, :<, 0) 73 | end 74 | 75 | def assert_infinite_calculation(positive:) 76 | BigDecimal.save_exception_mode do 77 | BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, false) 78 | positive ? assert_positive_infinite(yield) : assert_negative_infinite(yield) 79 | BigDecimal.mode(BigDecimal::EXCEPTION_INFINITY, true) 80 | assert_raise_with_message(FloatDomainError, /Infinity/) { yield } 81 | end 82 | end 83 | 84 | def assert_positive_infinite_calculation(&block) 85 | assert_infinite_calculation(positive: true, &block) 86 | end 87 | 88 | def assert_negative_infinite_calculation(&block) 89 | assert_infinite_calculation(positive: false, &block) 90 | end 91 | 92 | def assert_nan_calculation(&block) 93 | BigDecimal.save_exception_mode do 94 | BigDecimal.mode(BigDecimal::EXCEPTION_NaN, false) 95 | assert_nan(yield) 96 | BigDecimal.mode(BigDecimal::EXCEPTION_NaN, true) 97 | assert_raise_with_message(FloatDomainError, /NaN/) { yield } 98 | end 99 | end 100 | 101 | def assert_positive_zero(x) 102 | assert_equal(BigDecimal::SIGN_POSITIVE_ZERO, x.sign, 103 | "Expected #{x.inspect} to be positive zero") 104 | end 105 | 106 | def assert_negative_zero(x) 107 | assert_equal(BigDecimal::SIGN_NEGATIVE_ZERO, x.sign, 108 | "Expected #{x.inspect} to be negative zero") 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /test/bigdecimal/test_jruby.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require_relative 'helper' 3 | require 'bigdecimal/math' 4 | 5 | class TestJRuby < Test::Unit::TestCase 6 | # JRuby uses its own native BigDecimal implementation 7 | # but uses the same BigMath module as CRuby. 8 | # These are test to ensure BigMath works correctly with JRuby's BigDecimal. 9 | # Also run on CRuby to ensure compatibility. 10 | 11 | N = 20 12 | 13 | def test_decimal_shift_polyfill 14 | assert_equal(BigDecimal('123.45e2'), BigDecimal('123.45')._decimal_shift(2)) 15 | assert_equal(BigDecimal('123.45e-2'), BigDecimal('123.45')._decimal_shift(-2)) 16 | assert_equal(BigDecimal('123.45e10000'), BigDecimal('123.45')._decimal_shift(10000)) 17 | assert_equal(BigDecimal('123.45e-10000'), BigDecimal('123.45')._decimal_shift(-10000)) 18 | end 19 | 20 | def test_sqrt 21 | sqrt2 = BigDecimal(2).sqrt(N) 22 | assert_in_delta(Math.sqrt(2), sqrt2) 23 | assert_in_delta(2, sqrt2 * sqrt2) 24 | end 25 | 26 | def test_exp 27 | assert_in_delta(Math.exp(2), BigMath.exp(BigDecimal(2), N)) 28 | assert_in_delta(Math.exp(2), BigMath.exp(2, N)) 29 | assert_in_delta(Math.exp(2.5), BigMath.exp(2.5, N)) 30 | assert_in_delta(Math.exp(2.5), BigMath.exp(2.5r, N)) 31 | end 32 | 33 | def test_log 34 | assert_in_delta(Math.log(2), BigMath.log(BigDecimal(2), N)) 35 | assert_in_delta(Math.log(2), BigMath.log(2, N)) 36 | assert_in_delta(Math.log(2.5), BigMath.log(2.5, N)) 37 | assert_in_delta(Math.log(2.5), BigMath.log(2.5r, N)) 38 | end 39 | 40 | def test_power 41 | x = BigDecimal(2) 42 | expected = 2 ** 2.5 43 | assert_in_delta(expected, x ** BigDecimal('2.5')) 44 | assert_in_delta(expected, x.sqrt(N) ** 5) 45 | assert_in_delta(expected, x ** 2.5) 46 | assert_in_delta(expected, x ** 2.5r) 47 | assert_in_delta(expected, x.power(BigDecimal('2.5'), N)) 48 | assert_in_delta(expected, x.power(2.5, N)) 49 | assert_in_delta(expected, x.sqrt(N).power(5, N)) 50 | assert_in_delta(expected, x.power(2.5r, N)) 51 | end 52 | 53 | def test_bigmath 54 | assert_in_delta(Math.sqrt(2), BigMath.sqrt(BigDecimal(2), N)) 55 | assert_in_delta(Math.cbrt(2), BigMath.cbrt(BigDecimal(2), N)) 56 | assert_in_delta(Math.hypot(2, 3), BigMath.hypot(BigDecimal(2), BigDecimal(3), N)) 57 | assert_in_delta(Math.sin(1), BigMath.sin(BigDecimal(1), N)) 58 | assert_in_delta(Math.cos(1), BigMath.cos(BigDecimal(1), N)) 59 | assert_in_delta(Math.tan(1), BigMath.tan(BigDecimal(1), N)) 60 | assert_in_delta(Math.asin(0.5), BigMath.asin(BigDecimal('0.5'), N)) 61 | assert_in_delta(Math.acos(0.5), BigMath.acos(BigDecimal('0.5'), N)) 62 | assert_in_delta(Math.atan(1), BigMath.atan(BigDecimal(1), N)) 63 | assert_in_delta(Math.atan2(1, 2), BigMath.atan2(BigDecimal(1), BigDecimal(2), N)) 64 | assert_in_delta(Math.sinh(1), BigMath.sinh(BigDecimal(1), N)) 65 | assert_in_delta(Math.cosh(1), BigMath.cosh(BigDecimal(1), N)) 66 | assert_in_delta(Math.tanh(1), BigMath.tanh(BigDecimal(1), N)) 67 | assert_in_delta(Math.asinh(1), BigMath.asinh(BigDecimal(1), N)) 68 | assert_in_delta(Math.acosh(2), BigMath.acosh(BigDecimal(2), N)) 69 | assert_in_delta(Math.atanh(0.5), BigMath.atanh(BigDecimal('0.5'), N)) 70 | assert_in_delta(Math.log2(3), BigMath.log2(BigDecimal(3), N)) 71 | assert_in_delta(Math.log10(3), BigMath.log10(BigDecimal(3), N)) 72 | assert_in_delta(Math.log1p(0.1), BigMath.log1p(BigDecimal('0.1'), N)) if defined? Math.log1p 73 | assert_in_delta(Math.expm1(0.1), BigMath.expm1(BigDecimal('0.1'), N)) if defined? Math.expm1 74 | assert_in_delta(Math.erf(1), BigMath.erf(BigDecimal(1), N)) 75 | assert_in_delta(Math.erfc(10), BigMath.erfc(BigDecimal(10), N)) 76 | assert_in_delta(Math.gamma(0.5), BigMath.gamma(BigDecimal('0.5'), N)) 77 | assert_in_delta(Math.lgamma(0.5).first, BigMath.lgamma(BigDecimal('0.5'), N).first) 78 | assert_equal([BigDecimal('0.123'), 4], BigMath.frexp(BigDecimal('0.123e4'))) 79 | assert_equal(BigDecimal('12.3e4'), BigMath.ldexp(BigDecimal('12.3'), 4)) 80 | assert_in_delta(Math::PI, BigMath.PI(N)) 81 | assert_in_delta(Math::E, BigMath.E(N)) 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /ext/bigdecimal/bits.h: -------------------------------------------------------------------------------- 1 | #ifndef BIGDECIMAL_BITS_H 2 | #define BIGDECIMAL_BITS_H 3 | 4 | #include "feature.h" 5 | #include "static_assert.h" 6 | 7 | #if defined(__x86_64__) && defined(HAVE_X86INTRIN_H) 8 | # include /* for _lzcnt_u64, etc. */ 9 | #elif defined(_MSC_VER) && defined(HAVE_INTRIN_H) 10 | # include /* for the following intrinsics */ 11 | #endif 12 | 13 | #if defined(_MSC_VER) && defined(__AVX2__) 14 | # pragma intrinsic(__lzcnt) 15 | # pragma intrinsic(__lzcnt64) 16 | #endif 17 | 18 | #define numberof(array) ((int)(sizeof(array) / sizeof((array)[0]))) 19 | #define roomof(x, y) (((x) + (y) - 1) / (y)) 20 | #define type_roomof(x, y) roomof(sizeof(x), sizeof(y)) 21 | 22 | #define MUL_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ 23 | (a) == 0 ? 0 : \ 24 | (a) == -1 ? (b) < -(max) : \ 25 | (a) > 0 ? \ 26 | ((b) > 0 ? (max) / (a) < (b) : (min) / (a) > (b)) : \ 27 | ((b) > 0 ? (min) / (a) < (b) : (max) / (a) > (b))) 28 | 29 | #define ADD_OVERFLOW_SIGNED_INTEGER_P(a, b, min, max) ( \ 30 | ((a) > 0) == ((b) > 0) && ((a) > 0 ? (max) - (a) < (b) : (min) - (a) > (b))) 31 | 32 | #ifdef HAVE_UINT128_T 33 | # define bit_length(x) \ 34 | (unsigned int) \ 35 | (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ 36 | sizeof(x) <= sizeof(int64_t) ? 64 - nlz_int64((uint64_t)(x)) : \ 37 | 128 - nlz_int128((uint128_t)(x))) 38 | #else 39 | # define bit_length(x) \ 40 | (unsigned int) \ 41 | (sizeof(x) <= sizeof(int32_t) ? 32 - nlz_int32((uint32_t)(x)) : \ 42 | 64 - nlz_int64((uint64_t)(x))) 43 | #endif 44 | 45 | static inline unsigned nlz_int32(uint32_t x); 46 | static inline unsigned nlz_int64(uint64_t x); 47 | #ifdef HAVE_UINT128_T 48 | static inline unsigned nlz_int128(uint128_t x); 49 | #endif 50 | 51 | static inline unsigned int 52 | nlz_int32(uint32_t x) 53 | { 54 | #if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT) 55 | /* Note: It seems there is no such thing like __LZCNT__ predefined in MSVC. 56 | * AMD CPUs have had this instruction for decades (since K10) but for 57 | * Intel, Haswell is the oldest one. We need to use __AVX2__ for maximum 58 | * safety. */ 59 | return (unsigned int)__lzcnt(x); 60 | 61 | #elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U32) 62 | return (unsigned int)_lzcnt_u32(x); 63 | 64 | #elif defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE) 65 | unsigned long r; 66 | return _BitScanReverse(&r, x) ? (31 - (int)r) : 32; 67 | 68 | #elif __has_builtin(__builtin_clz) 69 | STATIC_ASSERT(sizeof_int, sizeof(int) * CHAR_BIT == 32); 70 | return x ? (unsigned int)__builtin_clz(x) : 32; 71 | 72 | #else 73 | uint32_t y; 74 | unsigned n = 32; 75 | y = x >> 16; if (y) {n -= 16; x = y;} 76 | y = x >> 8; if (y) {n -= 8; x = y;} 77 | y = x >> 4; if (y) {n -= 4; x = y;} 78 | y = x >> 2; if (y) {n -= 2; x = y;} 79 | y = x >> 1; if (y) {return n - 2;} 80 | return (unsigned int)(n - x); 81 | #endif 82 | } 83 | 84 | static inline unsigned int 85 | nlz_int64(uint64_t x) 86 | { 87 | #if defined(_MSC_VER) && defined(__AVX2__) && defined(HAVE___LZCNT64) 88 | return (unsigned int)__lzcnt64(x); 89 | 90 | #elif defined(__x86_64__) && defined(__LZCNT__) && defined(HAVE__LZCNT_U64) 91 | return (unsigned int)_lzcnt_u64(x); 92 | 93 | #elif defined(_WIN64) && defined(_MSC_VER) && defined(HAVE__BITSCANREVERSE64) 94 | unsigned long r; 95 | return _BitScanReverse64(&r, x) ? (63u - (unsigned int)r) : 64; 96 | 97 | #elif __has_builtin(__builtin_clzl) && __has_builtin(__builtin_clzll) && !(defined(__sun) && defined(__sparc)) 98 | if (x == 0) { 99 | return 64; 100 | } 101 | else if (sizeof(long) * CHAR_BIT == 64) { 102 | return (unsigned int)__builtin_clzl((unsigned long)x); 103 | } 104 | else if (sizeof(long long) * CHAR_BIT == 64) { 105 | return (unsigned int)__builtin_clzll((unsigned long long)x); 106 | } 107 | else { 108 | /* :FIXME: Is there a way to make this branch a compile-time error? */ 109 | __builtin_unreachable(); 110 | } 111 | 112 | #else 113 | uint64_t y; 114 | unsigned int n = 64; 115 | y = x >> 32; if (y) {n -= 32; x = y;} 116 | y = x >> 16; if (y) {n -= 16; x = y;} 117 | y = x >> 8; if (y) {n -= 8; x = y;} 118 | y = x >> 4; if (y) {n -= 4; x = y;} 119 | y = x >> 2; if (y) {n -= 2; x = y;} 120 | y = x >> 1; if (y) {return n - 2;} 121 | return (unsigned int)(n - x); 122 | 123 | #endif 124 | } 125 | 126 | #ifdef HAVE_UINT128_T 127 | static inline unsigned int 128 | nlz_int128(uint128_t x) 129 | { 130 | uint64_t y = (uint64_t)(x >> 64); 131 | 132 | if (x == 0) { 133 | return 128; 134 | } 135 | else if (y == 0) { 136 | return (unsigned int)nlz_int64(x) + 64; 137 | } 138 | else { 139 | return (unsigned int)nlz_int64(y); 140 | } 141 | } 142 | #endif 143 | 144 | #endif /* BIGDECIMAL_BITS_H */ 145 | -------------------------------------------------------------------------------- /lib/bigdecimal/util.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # 3 | #-- 4 | # bigdecimal/util extends various native classes to provide the #to_d method, 5 | # and provides BigDecimal#to_d and BigDecimal#to_digits. 6 | #++ 7 | 8 | require 'bigdecimal' 9 | 10 | class Integer < Numeric 11 | # call-seq: 12 | # int.to_d -> bigdecimal 13 | # 14 | # Returns the value of +int+ as a BigDecimal. 15 | # 16 | # require 'bigdecimal' 17 | # require 'bigdecimal/util' 18 | # 19 | # 42.to_d # => 0.42e2 20 | # 21 | # See also Kernel.BigDecimal. 22 | # 23 | def to_d 24 | BigDecimal(self) 25 | end 26 | end 27 | 28 | 29 | class Float < Numeric 30 | # call-seq: 31 | # float.to_d -> bigdecimal 32 | # float.to_d(precision) -> bigdecimal 33 | # 34 | # Returns the value of +float+ as a BigDecimal. 35 | # The +precision+ parameter is used to determine the number of 36 | # significant digits for the result. When +precision+ is set to +0+, 37 | # the number of digits to represent the float being converted is determined 38 | # automatically. 39 | # The default +precision+ is +0+. 40 | # 41 | # require 'bigdecimal' 42 | # require 'bigdecimal/util' 43 | # 44 | # 0.5.to_d # => 0.5e0 45 | # 1.234.to_d # => 0.1234e1 46 | # 1.234.to_d(2) # => 0.12e1 47 | # 48 | # See also Kernel.BigDecimal. 49 | # 50 | def to_d(precision=0) 51 | BigDecimal(self, precision) 52 | end 53 | end 54 | 55 | 56 | class String 57 | # call-seq: 58 | # str.to_d -> bigdecimal 59 | # 60 | # Returns the result of interpreting leading characters in +str+ 61 | # as a BigDecimal. 62 | # 63 | # require 'bigdecimal' 64 | # require 'bigdecimal/util' 65 | # 66 | # "0.5".to_d # => 0.5e0 67 | # "123.45e1".to_d # => 0.12345e4 68 | # "45.67 degrees".to_d # => 0.4567e2 69 | # 70 | # See also Kernel.BigDecimal. 71 | # 72 | def to_d 73 | BigDecimal.interpret_loosely(self) 74 | end 75 | end 76 | 77 | 78 | class BigDecimal < Numeric 79 | # call-seq: 80 | # a.to_digits -> string 81 | # 82 | # Converts a BigDecimal to a String of the form "nnnnnn.mmm". 83 | # This method is deprecated; use BigDecimal#to_s("F") instead. 84 | # 85 | # require 'bigdecimal/util' 86 | # 87 | # d = BigDecimal("3.14") 88 | # d.to_digits # => "3.14" 89 | # 90 | def to_digits 91 | if self.nan? || self.infinite? || self.zero? 92 | self.to_s 93 | else 94 | i = self.to_i.to_s 95 | _,f,_,z = self.frac.split 96 | i + "." + ("0"*(-z)) + f 97 | end 98 | end 99 | 100 | # call-seq: 101 | # a.to_d -> bigdecimal 102 | # 103 | # Returns self. 104 | # 105 | # require 'bigdecimal/util' 106 | # 107 | # d = BigDecimal("3.14") 108 | # d.to_d # => 0.314e1 109 | # 110 | def to_d 111 | self 112 | end 113 | end 114 | 115 | 116 | class Rational < Numeric 117 | # call-seq: 118 | # rat.to_d(precision) -> bigdecimal 119 | # 120 | # Returns the value as a BigDecimal. 121 | # 122 | # The +precision+ parameter is used to determine the number of 123 | # significant digits for the result. When +precision+ is set to +0+, 124 | # the number of digits to represent the float being converted is determined 125 | # automatically. 126 | # The default +precision+ is +0+. 127 | # 128 | # require 'bigdecimal' 129 | # require 'bigdecimal/util' 130 | # 131 | # Rational(22, 7).to_d(3) # => 0.314e1 132 | # 133 | # See also Kernel.BigDecimal. 134 | # 135 | def to_d(precision=0) 136 | BigDecimal(self, precision) 137 | end 138 | end 139 | 140 | 141 | class Complex < Numeric 142 | # call-seq: 143 | # cmp.to_d -> bigdecimal 144 | # cmp.to_d(precision) -> bigdecimal 145 | # 146 | # Returns the value as a BigDecimal. 147 | # If the imaginary part is not +0+, an error is raised 148 | # 149 | # The +precision+ parameter is used to determine the number of 150 | # significant digits for the result. When +precision+ is set to +0+, 151 | # the number of digits to represent the float being converted is determined 152 | # automatically. 153 | # The default +precision+ is +0+. 154 | # 155 | # require 'bigdecimal' 156 | # require 'bigdecimal/util' 157 | # 158 | # Complex(0.1234567, 0).to_d(4) # => 0.1235e0 159 | # Complex(Rational(22, 7), 0).to_d(3) # => 0.314e1 160 | # Complex(1, 1).to_d # raises ArgumentError 161 | # 162 | # See also Kernel.BigDecimal. 163 | # 164 | def to_d(precision=0) 165 | BigDecimal(self) unless self.imag.zero? # to raise error 166 | 167 | BigDecimal(self.real, precision) 168 | end 169 | end 170 | 171 | 172 | class NilClass 173 | # call-seq: 174 | # nil.to_d -> bigdecimal 175 | # 176 | # Returns nil represented as a BigDecimal. 177 | # 178 | # require 'bigdecimal' 179 | # require 'bigdecimal/util' 180 | # 181 | # nil.to_d # => 0.0 182 | # 183 | def to_d 184 | BigDecimal(0) 185 | end 186 | end 187 | -------------------------------------------------------------------------------- /test/bigdecimal/test_bigdecimal_util.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require_relative "helper" 3 | require 'bigdecimal/util' 4 | 5 | class TestBigDecimalUtil < Test::Unit::TestCase 6 | include TestBigDecimalBase 7 | 8 | def test_BigDecimal_to_d 9 | x = BigDecimal(1) 10 | assert_same(x, x.to_d) 11 | end 12 | 13 | def test_Integer_to_d 14 | assert_equal(BigDecimal(1), 1.to_d) 15 | assert_equal(BigDecimal(2<<100), (2<<100).to_d) 16 | 17 | assert(1.to_d.frozen?) 18 | end 19 | 20 | def test_Float_to_d_without_precision 21 | delta = 1.0/10**(Float::DIG+1) 22 | assert_in_delta(BigDecimal(0.5, 0), 0.5.to_d, delta) 23 | assert_in_delta(BigDecimal(355.0/113.0, 0), (355.0/113.0).to_d, delta) 24 | 25 | assert_equal(9.05, 9.05.to_d.to_f) 26 | assert_equal("9.05", 9.05.to_d.to_s('F')) 27 | 28 | assert_equal("65.6", 65.6.to_d.to_s("F")) 29 | 30 | assert_equal(Math::PI, Math::PI.to_d.to_f) 31 | 32 | bug9214 = '[ruby-core:58858]' 33 | assert_equal((-0.0).to_d.sign, -1, bug9214) 34 | 35 | assert_raise(TypeError) { 0.3.to_d(nil) } 36 | assert_raise(TypeError) { 0.3.to_d(false) } 37 | 38 | assert(1.1.to_d.frozen?) 39 | 40 | assert_equal(BigDecimal("999_999.9999"), 999_999.9999.to_d) 41 | end 42 | 43 | def test_Float_to_d_with_precision 44 | digits = 5 45 | delta = 1.0/10**(digits) 46 | assert_in_delta(BigDecimal(0.5, 5), 0.5.to_d(digits), delta) 47 | assert_in_delta(BigDecimal(355.0/113.0, 5), (355.0/113.0).to_d(digits), delta) 48 | 49 | bug9214 = '[ruby-core:58858]' 50 | assert_equal((-0.0).to_d(digits).sign, -1, bug9214) 51 | 52 | assert(1.1.to_d(digits).frozen?) 53 | end 54 | 55 | def test_Float_to_d_bug13331 56 | assert_equal(64.4.to_d, 57 | 1.to_d * 64.4, 58 | "[ruby-core:80234] [Bug #13331]") 59 | 60 | assert_equal((2*Math::PI).to_d, 61 | 2.to_d * Math::PI, 62 | "[ruby-core:80234] [Bug #13331]") 63 | end 64 | 65 | def test_Float_to_d_issue_192 66 | # https://github.com/ruby/bigdecimal/issues/192 67 | # https://github.com/rails/rails/pull/42125 68 | if BASE_FIG == 9 69 | flo = 1_000_000_000.12345 70 | big = BigDecimal("0.100000000012345e10") 71 | else # BASE_FIG == 4 72 | flo = 1_0000.12 73 | big = BigDecimal("0.1000012e5") 74 | end 75 | assert_equal(flo.to_d, big, "[ruby/bigdecimal#192]") 76 | end 77 | 78 | def test_Rational_to_d 79 | digits = 100 80 | delta = 1.0/10**(digits) 81 | assert_in_delta(BigDecimal(1.quo(2), digits), 1.quo(2).to_d(digits), delta) 82 | assert_in_delta(BigDecimal(355.quo(113), digits), 355.quo(113).to_d(digits), delta) 83 | 84 | assert(355.quo(113).to_d(digits).frozen?) 85 | end 86 | 87 | def test_Rational_to_d_without_precision 88 | assert_equal(BigDecimal("1.25"), Rational(5, 4).to_d) 89 | assert_equal(BigDecimal(355.quo(113), 0), 355.quo(113).to_d) 90 | end 91 | 92 | def test_Rational_to_d_with_zero_precision 93 | assert_equal(BigDecimal(355.quo(113), 0), 355.quo(113).to_d(0)) 94 | end 95 | 96 | def test_Rational_to_d_with_negative_precision 97 | assert_raise(ArgumentError) { 355.quo(113).to_d(-42) } 98 | end 99 | 100 | def test_Complex_to_d 101 | BigDecimal.save_rounding_mode do 102 | BigDecimal.mode(BigDecimal::ROUND_MODE, BigDecimal::ROUND_HALF_EVEN) 103 | 104 | assert_equal(BigDecimal("1"), Complex(1, 0).to_d) 105 | assert_equal(BigDecimal("0.333333333333333333333"), 106 | Complex(1.quo(3), 0).to_d(21)) 107 | assert_equal(BigDecimal("0.1234567"), Complex(0.1234567, 0).to_d) 108 | assert_equal(BigDecimal("0.1235"), Complex(0.1234567, 0).to_d(4)) 109 | 110 | assert_equal(BigDecimal("0.5"), Complex(1.quo(2), 0).to_d) 111 | 112 | assert_raise_with_message(ArgumentError, "Unable to make a BigDecimal from non-zero imaginary number") { Complex(1, 1).to_d } 113 | end 114 | end 115 | 116 | def test_String_to_d 117 | assert_equal(BigDecimal('1'), "1__1_1".to_d) 118 | assert_equal(BigDecimal('2.5'), "2.5".to_d) 119 | assert_equal(BigDecimal('2.5'), "2.5 degrees".to_d) 120 | assert_equal(BigDecimal('2.5e1'), "2.5e1 degrees".to_d) 121 | assert_equal(BigDecimal('0'), "degrees 100.0".to_d) 122 | assert_equal(BigDecimal('0.125'), "0.1_2_5".to_d) 123 | assert_equal(BigDecimal('0.125'), "0.1_2_5__".to_d) 124 | assert_equal(BigDecimal('1'), "1_.125".to_d) 125 | assert_equal(BigDecimal('1'), "1._125".to_d) 126 | assert_equal(BigDecimal('0.1'), "0.1__2_5".to_d) 127 | assert_equal(BigDecimal('0.1'), "0.1_e10".to_d) 128 | assert_equal(BigDecimal('0.1'), "0.1e_10".to_d) 129 | assert_equal(BigDecimal('1'), "0.1e1__0".to_d) 130 | assert_equal(BigDecimal('1.2'), "1.2.3".to_d) 131 | assert_equal(BigDecimal('1'), "1.".to_d) 132 | assert_equal(BigDecimal('1'), "1e".to_d) 133 | 134 | assert("2.5".to_d.frozen?) 135 | end 136 | 137 | def test_invalid_String_to_d 138 | assert_equal("invalid".to_d, BigDecimal('0.0')) 139 | end 140 | 141 | def test_Nil_to_d 142 | assert_equal(nil.to_d, BigDecimal('0.0')) 143 | 144 | assert(nil.to_d) 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /test/bigdecimal/test_vp_operation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require_relative 'helper' 3 | require 'bigdecimal' 4 | 5 | class TestVpOperation < Test::Unit::TestCase 6 | include TestBigDecimalBase 7 | 8 | def setup 9 | super 10 | unless BigDecimal.instance_methods.include?(:vpdivd) 11 | # rake clean && BIGDECIMAL_USE_VP_TEST_METHODS=true rake compile 12 | omit 'Compile with BIGDECIMAL_USE_VP_TEST_METHODS=true to run this test' 13 | end 14 | end 15 | 16 | def test_vpmult 17 | assert_equal(BigDecimal('121932631112635269'), BigDecimal('123456789').vpmult(BigDecimal('987654321'))) 18 | assert_equal(BigDecimal('12193263.1112635269'), BigDecimal('123.456789').vpmult(BigDecimal('98765.4321'))) 19 | x = 123**456 20 | y = 987**123 21 | assert_equal(BigDecimal("#{x * y}e-300"), BigDecimal("#{x}e-100").vpmult(BigDecimal("#{y}e-200"))) 22 | end 23 | 24 | def test_vpdivd 25 | # a[0] > b[0] 26 | # XXXX_YYYY_ZZZZ / 1111 #=> 000X_000Y_000Z 27 | x1 = BigDecimal('2' * BASE_FIG + '3' * BASE_FIG + '4' * BASE_FIG + '5' * BASE_FIG + '6' * BASE_FIG) 28 | y = BigDecimal('1' * BASE_FIG) 29 | d1 = BigDecimal("2e#{BASE_FIG * 4}") 30 | d2 = BigDecimal("3e#{BASE_FIG * 3}") + d1 31 | d3 = BigDecimal("4e#{BASE_FIG * 2}") + d2 32 | d4 = BigDecimal("5e#{BASE_FIG}") + d3 33 | d5 = BigDecimal(6) + d4 34 | assert_equal([d1, x1 - d1 * y], x1.vpdivd(y, 1)) 35 | assert_equal([d2, x1 - d2 * y], x1.vpdivd(y, 2)) 36 | assert_equal([d3, x1 - d3 * y], x1.vpdivd(y, 3)) 37 | assert_equal([d4, x1 - d4 * y], x1.vpdivd(y, 4)) 38 | assert_equal([d5, x1 - d5 * y], x1.vpdivd(y, 5)) 39 | 40 | # a[0] < b[0] 41 | # 00XX_XXYY_YYZZ_ZZ00 / 1111 #=> 0000_0X00_0Y00_0Z00 42 | shift = BASE_FIG / 2 43 | x2 = BigDecimal('2' * BASE_FIG + '3' * BASE_FIG + '4' * BASE_FIG + '5' * BASE_FIG + '6' * BASE_FIG + '0' * shift) 44 | d1 = BigDecimal("2e#{4 * BASE_FIG + shift}") 45 | d2 = BigDecimal("3e#{3 * BASE_FIG + shift}") + d1 46 | d3 = BigDecimal("4e#{2 * BASE_FIG + shift}") + d2 47 | d4 = BigDecimal("5e#{BASE_FIG + shift}") + d3 48 | d5 = BigDecimal("6e#{shift}") + d4 49 | assert_equal([0, x2], x2.vpdivd(y, 1)) 50 | assert_equal([d1, x2 - d1 * y], x2.vpdivd(y, 2)) 51 | assert_equal([d2, x2 - d2 * y], x2.vpdivd(y, 3)) 52 | assert_equal([d3, x2 - d3 * y], x2.vpdivd(y, 4)) 53 | assert_equal([d4, x2 - d4 * y], x2.vpdivd(y, 5)) 54 | assert_equal([d5, x2 - d5 * y], x2.vpdivd(y, 6)) 55 | end 56 | 57 | def test_vpdivd_large_quotient_prec 58 | # 0001 / 0003 = 0000_3333_3333 59 | assert_equal([BigDecimal('0.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(1).vpdivd(BigDecimal(3), 10)) 60 | # 1000 / 0003 = 0333_3333_3333 61 | assert_equal([BigDecimal('3' * (BASE_FIG - 1) + '.' + '3' * BASE_FIG * 9), BigDecimal("1e-#{9 * BASE_FIG}")], BigDecimal(BASE / 10).vpdivd(BigDecimal(3), 10)) 62 | end 63 | 64 | def test_vpdivd_with_one 65 | x = BigDecimal('1234.2468000001234') 66 | assert_equal([BigDecimal('1234'), BigDecimal('0.2468000001234')], x.vpdivd(BigDecimal(1), 1)) 67 | assert_equal([BigDecimal('+1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(+1), 2)) 68 | assert_equal([BigDecimal('-1234.2468'), BigDecimal('+0.1234e-9')], (+x).vpdivd(BigDecimal(-1), 2)) 69 | assert_equal([BigDecimal('-1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(+1), 2)) 70 | assert_equal([BigDecimal('+1234.2468'), BigDecimal('-0.1234e-9')], (-x).vpdivd(BigDecimal(-1), 2)) 71 | end 72 | 73 | def test_vpdivd_precisions 74 | xs = [5, 10, 20, 40].map {|n| 123 ** n } 75 | ys = [5, 10, 20, 40].map {|n| 321 ** n } 76 | xs.product(ys).each do |x, y| 77 | [1, 2, 10, 20].each do |n| 78 | xn = (x.digits.size + BASE_FIG - 1) / BASE_FIG 79 | yn = (y.digits.size + BASE_FIG - 1) / BASE_FIG 80 | base = BASE ** (n - xn + yn - 1) 81 | div = BigDecimal((x * base / y).to_i) / base 82 | assert_equal([div, x - y * div], BigDecimal(x).vpdivd(y, n)) 83 | end 84 | end 85 | end 86 | 87 | def test_vpdivd_borrow 88 | y_small = BASE / 7 * BASE ** 4 89 | y_large = (4 * BASE_FIG).times.map {|i| i % 9 + 1 }.join.to_i 90 | [y_large, y_small].each do |y| 91 | [0, 1, 2, BASE - 2, BASE - 1].repeated_permutation(4) do |a, b, c, d| 92 | x = y * (3 * BASE**4 + a * BASE**3 + b * BASE**2 + c * BASE + d) / BASE 93 | div = BigDecimal(x * BASE / y) / BASE 94 | mod = BigDecimal(x) - div * y 95 | assert_equal([div, mod], BigDecimal(x).vpdivd(BigDecimal(y), 5)) 96 | end 97 | end 98 | end 99 | 100 | def test_vpdivd_large_prec_divisor 101 | x = BigDecimal('2468.000000000000000000000000003') 102 | y1 = BigDecimal('1234.000000000000000000000000001') 103 | y2 = BigDecimal('1234.000000000000000000000000004') 104 | divy1_1 = BigDecimal(2) 105 | divy2_1 = BigDecimal(1) 106 | divy2_2 = BigDecimal('1.' + '9' * BASE_FIG) 107 | assert_equal([divy1_1, x - y1 * divy1_1], x.vpdivd(y1, 1)) 108 | assert_equal([divy2_1, x - y2 * divy2_1], x.vpdivd(y2, 1)) 109 | assert_equal([divy2_2, x - y2 * divy2_2], x.vpdivd(y2, 2)) 110 | end 111 | 112 | def test_vpdivd_intermediate_zero 113 | if BASE_FIG == 9 114 | x = BigDecimal('123456789.246913578000000000123456789') 115 | y = BigDecimal('123456789') 116 | assert_equal([BigDecimal('1.000000002000000000000000001'), BigDecimal(0)], x.vpdivd(y, 4)) 117 | assert_equal([BigDecimal('1.000000000049999999'), BigDecimal('1e-18')], BigDecimal("2.000000000099999999").vpdivd(2, 3)) 118 | else 119 | x = BigDecimal('1234.246800001234') 120 | y = BigDecimal('1234') 121 | assert_equal([BigDecimal('1.000200000001'), BigDecimal(0)], x.vpdivd(y, 4)) 122 | assert_equal([BigDecimal('1.00000499'), BigDecimal('1e-8')], BigDecimal("2.00000999").vpdivd(2, 3)) 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # CHANGES 2 | 3 | ## 3.3.1 4 | 5 | * All BigMath methods converts non integer precision with to_int 6 | 7 | **@tompng** 8 | 9 | ## 3.3.0 10 | 11 | * Allow calling to_d without arguments [GH-421] 12 | 13 | **@fsateler** 14 | 15 | * Calculate BigMath.sin and cos in relative precision [GH-422] 16 | 17 | **@tompng** 18 | 19 | * Add support for tangent function [GH-231] 20 | 21 | **@rhannequin** 22 | 23 | * BigMath methods accepts numeric as an argument [GH-415] 24 | 25 | **@tompng** 26 | 27 | * Round result of sqrt and BigMath methods [GH-427] 28 | 29 | **@tompng** 30 | 31 | ## 3.2.3 32 | 33 | * Allow BigDecimal accept Float without precision [GH-314] 34 | 35 | **@mrzasa** 36 | 37 | * Ruby implementation pow, log, exp and sqrt [GH-347] [GH-381] 38 | 39 | **@tompng** 40 | 41 | * Update document [GH-348] [GH-360] [GH-365] 42 | 43 | **@timcraft** **@dduugg** **@mame** 44 | 45 | * Lots of bug fixes and refactoring 46 | 47 | ## 3.2.2 48 | 49 | * Make precision calculation in bigdecimal.div(value, 0) gc-compaction safe. [GH-340] 50 | 51 | **@tompng** 52 | 53 | ## 3.2.1 54 | 55 | * Fix division precision limit. [GH-335] 56 | 57 | **@tompng** 58 | 59 | ## 3.2.0 60 | 61 | * Fix division rounding. [GH-330] [GH-328] 62 | 63 | **@tompng** 64 | 65 | * Fix exponential precision growth in division. [GH-329] [GH-220] [GH-222] [GH-272] 66 | 67 | **@tompng** 68 | 69 | ## 3.1.9 70 | 71 | * Accept no digits in the fractional part (#302) 72 | 73 | **@kou** 74 | 75 | ## 3.1.8 76 | 77 | * Add missing documents [GH-277] 78 | 79 | **@nobu** 80 | 81 | * Fix memory leak in VpAlloc [GH-294] [GH-290] 82 | 83 | Reported by **@MaxLap** 84 | 85 | ## 3.1.7 86 | 87 | * Only consists of CI settings and test changes. 88 | This release is needed for developing Ruby to run `make test-bundled-gems` with the released version of bigdecimal. 89 | 90 | ## 3.1.6 91 | 92 | * Add LICENSE file to gem files [GH-282] 93 | 94 | **@oleksii-leonov** 95 | 96 | ## 3.1.5 97 | 98 | * Add .to_s('F') digit grouping for integer part [GH-264] 99 | 100 | **@cryptogopher** 101 | 102 | ## 3.1.4 103 | 104 | * Handle correctly #reminder with infinity. Fixes [GH-187] [GH-243] 105 | 106 | **@mrzasa** 107 | 108 | ## 3.1.3 109 | 110 | * Adjust a local variable type to exponent. [GH-223] 111 | * Remove checks for `struct RRational` and `struct RComplex` . [GH-233] 112 | * Suppress macro redefinition warnings. [GH-239] 113 | 114 | ## 3.1.2 115 | 116 | * Fix the maximum precision of the quotient. [GH-220] 117 | 118 | Reported by @grk 119 | 120 | ## 3.1.1 121 | 122 | * Fix the result precision of `BigDecimal#divmod`. [GH-219] 123 | 124 | **Kenta Murata** 125 | 126 | ## 3.1.0 127 | 128 | * Improve documentation [GH-209] 129 | 130 | **Burdette Lamar** 131 | 132 | * Let BigDecimal#quo accept precision. [GH-214] [Bug #8826] 133 | 134 | Reported by Földes László 135 | 136 | * Allow passing both float and precision in BigDecimal#div. [GH-212] [Bug #8826] 137 | 138 | Reported by Földes László 139 | 140 | * Add `BigDecimal#scale` and `BigDecimal#precision_scale` 141 | 142 | **Kenta Murata** 143 | 144 | * Fix a bug of `BigDecimal#precision` for the case that a BigDecimal has single internal digit [GH-205] 145 | 146 | **Kenta Murata** 147 | 148 | * Fix segmentation fault due to a bug of `VpReallocReal` 149 | 150 | **Kenta Murata** 151 | 152 | * Use larger precision in divide for irrational or recurring results. [GH-94] [Bug #13754] 153 | 154 | Reported by Lionel PERRIN 155 | 156 | * Fix negative Bignum conversion [GH-196] 157 | 158 | **Jean byroot Boussier** 159 | 160 | * Fix grammar in error messages. [GH-196] 161 | 162 | **Olle Jonsson** 163 | 164 | * Improve the conversion speed of `Kernel#BigDecimal` and `to_d` methods. 165 | 166 | **Kenta Murata** 167 | 168 | * Fix trailing zeros handling in `rb_uint64_convert_to_BigDecimal`. [GH-192] 169 | 170 | Reported by @kamipo 171 | 172 | * Permit 0 digits in `BigDecimal(float)` and `Float#to_d`. 173 | It means auto-detection of the smallest number of digits to represent 174 | the given Float number without error. [GH-180] 175 | 176 | **Kenta Murata** 177 | 178 | * Fix precision issue of Float. [GH-70] [Bug #13331] 179 | 180 | Reported by @casperisfine 181 | 182 | ## 3.0.2 183 | 184 | *This version is totally same as 3.0.0. This was released for reverting 3.0.1.* 185 | 186 | * Revert the changes in 3.0.1 due to remaining bugs. 187 | 188 | ## 3.0.1 189 | 190 | *This version is yanked due to the remaining bugs.* 191 | 192 | ## 3.0.0 193 | 194 | * Deprecate `BigDecimal#precs`. 195 | 196 | **Kenta Murata** 197 | 198 | * Add `BigDecimal#n_significant_digits`. 199 | 200 | **Kenta Murata** 201 | 202 | * Add `BigDecimal#precision`. 203 | 204 | **Kenta Murata** 205 | 206 | * Ractor support. 207 | 208 | **Kenta Murata** 209 | 210 | * Fix a bug of the way to undefine `allocate` method. 211 | 212 | **Kenta Murata** 213 | 214 | * Fix the default precision of `Float#to_d`. 215 | [Bug #13331] 216 | 217 | **Kenta Murata** 218 | 219 | ## 2.0.2 220 | 221 | * Deprecate taint/trust and related methods, and make the methods no-ops 222 | 223 | **Jeremy Evans** 224 | 225 | * Make BigDecimal#round with argument < 1 return Integer 226 | 227 | **Jeremy Evans** 228 | 229 | * Use higher default precision for BigDecimal#power and #** 230 | 231 | **Jeremy Evans** 232 | **Kenta Murata** 233 | 234 | ## 2.0.1 235 | 236 | * Let BigDecimal#to_s return US-ASCII string 237 | 238 | **Kenta Murata** 239 | 240 | ## 2.0.0 241 | 242 | * Remove `BigDecimal.new` 243 | 244 | **Kenta Murata** 245 | 246 | * Drop fat-gem support 247 | 248 | **Akira Matsuda** 249 | 250 | * Do not mutate frozen BigDecimal argument in BigMath.exp 251 | 252 | **Jeremy Evans** 253 | 254 | * Make Kernel#BigDecimal return argument if given correct type 255 | [Bug #7522] 256 | 257 | **Jeremy Evans** 258 | 259 | * Undef BigDecimal#initialize_copy 260 | 261 | **Jeremy Evans** 262 | 263 | * Support conversion from Complex without the imaginary part 264 | 265 | **Kenta Murata** 266 | 267 | * Remove taint checking 268 | 269 | **Jeremy Evans** 270 | 271 | * Code maintenance 272 | 273 | **Nobuyoshi Nakada** 274 | 275 | ## 1.4.4 276 | 277 | * Fix String#to_d against the string with trailing "e" like "1e" 278 | 279 | **Ibrahim Awwal** 280 | 281 | * Add BigDecimal.interpret_loosely, use it in String#to_d, 282 | and remove bigdecimal/util.so and rmpd_util_str_to_d 283 | 284 | **Kenta Murata** 285 | 286 | ## 1.4.3 287 | 288 | * Restore subclassing support 289 | 290 | **Kenta Murata** 291 | 292 | ## 1.4.2 293 | 294 | * Fix gem installation issue on mingw32. 295 | 296 | **Kenta Murata** 297 | 298 | ## 1.4.1 299 | 300 | * Fix wrong packaging. 301 | 302 | **Ben Ford** 303 | 304 | ## 1.4.0 305 | 306 | * Update documentation of `exception:` keyword. 307 | 308 | **Victor Shepelev** 309 | 310 | * Fix VpAlloc so that '1.2.3'.to_d is 1.2 311 | 312 | **Nobuyoshi Nakada** 313 | 314 | * Restore `BigDecimal.new` just for version 1.4 for very old version of Rails 315 | 316 | **Kenta Murata** 317 | 318 | * Support `exception:` keyword in `BigDecimal()` 319 | 320 | **Kenta Murata** 321 | 322 | * Remove `BigDecimal#initialize` 323 | 324 | **Kenta Murata** 325 | 326 | * Fix the string parsing logic in `BigDecimal()` to follow `Float()` 327 | 328 | **Kenta Murata** 329 | 330 | * Fix `String#to_d` to follow `String#to_f` 331 | 332 | **Kenta Murata** 333 | 334 | * Update `BigDecimal#inspect` documentation 335 | 336 | **Dana Sherson** 337 | 338 | * Remove `BigDecimal.ver`, `BigDecimal.allocate`, and `BigDecimal.new` 339 | 340 | **Kenta Murata** 341 | 342 | * No more support Ruby < 2.3 343 | 344 | **Kenta Murata** 345 | 346 | * Make BigDecimal objects frozen 347 | 348 | **Kenta Murata** 349 | 350 | * Remove division by zero from the internal implementation 351 | 352 | **Urabe, Shyouhei** 353 | 354 | ## 1.3.5 355 | 356 | * Add NilClass#to_d 357 | 358 | **Jose Ramon Camacho** 359 | 360 | ## 1.3.4 361 | 362 | * Stop deprecation warning in dup and clone, and just return self 363 | 364 | **Kenta Murata** 365 | 366 | * Improve warning message in BigDecimal.new 367 | 368 | **Masataka Pocke Kuwabara** 369 | 370 | ## 1.3.3 371 | 372 | * Introduce BigDecimal::VERSION, deprecate BigDecimal.ver, and follow this version string to gem's version. 373 | 374 | **Kenta Murata** 375 | 376 | * Deprecate BigDecimal.new 377 | 378 | **Kenta Murata** 379 | 380 | * Deprecate BigDecimal#clone and #dup 381 | 382 | **Kenta Murata** 383 | 384 | * Relax the dependent version of rake-compiler 385 | 386 | **yui-knk** 387 | 388 | * Works for better windows cross-compilation support 389 | 390 | **SHIBATA Hiroshi** 391 | 392 | * Use Bundler::GemHelper.install_tasks instead of bundler/gem_tasks 393 | 394 | **SHIBATA Hiroshi** 395 | 396 | * Remove the old gemspec file 397 | 398 | **yui-knk** 399 | 400 | * Fix for mathn removal in Ruby 2.5 401 | 402 | **SHIBATA Hiroshi** 403 | 404 | * Update ruby versions in .travis.yml 405 | 406 | **Jun Aruga** 407 | 408 | * Add tests for BigDecimal#truncate 409 | 410 | **yui-knk** 411 | 412 | * Add tests for BigDecimal#round 413 | 414 | **yui-knk** 415 | 416 | * Fix error message for precision argument 417 | 418 | **Marcus Stollsteimer** 419 | 420 | ## 1.3.2 and older 421 | 422 | Omitted. 423 | -------------------------------------------------------------------------------- /ext/bigdecimal/bigdecimal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Ruby BigDecimal(Variable decimal precision) extension library. 4 | * 5 | * Copyright(C) 2002 by Shigeo Kobayashi(shigeo@tinyforest.gr.jp) 6 | * 7 | */ 8 | 9 | #ifndef RUBY_BIG_DECIMAL_H 10 | #define RUBY_BIG_DECIMAL_H 1 11 | 12 | #define RUBY_NO_OLD_COMPATIBILITY 13 | #include "ruby/ruby.h" 14 | #include "missing.h" 15 | 16 | #ifdef HAVE_FLOAT_H 17 | # include 18 | #endif 19 | 20 | #if defined(HAVE_INT64_T) && !defined(BIGDECIMAL_USE_DECDIG_UINT16_T) 21 | # define DECDIG uint32_t 22 | # define DECDIG_DBL uint64_t 23 | # define DECDIG_DBL_SIGNED int64_t 24 | # define SIZEOF_DECDIG 4 25 | # define PRI_DECDIG_PREFIX "" 26 | # ifdef PRI_LL_PREFIX 27 | # define PRI_DECDIG_DBL_PREFIX PRI_LL_PREFIX 28 | # else 29 | # define PRI_DECDIG_DBL_PREFIX "l" 30 | # endif 31 | #else 32 | # define DECDIG uint16_t 33 | # define DECDIG_DBL uint32_t 34 | # define DECDIG_DBL_SIGNED int32_t 35 | # define SIZEOF_DECDIG 2 36 | # define PRI_DECDIG_PREFIX "h" 37 | # define PRI_DECDIG_DBL_PREFIX "" 38 | #endif 39 | 40 | #define PRIdDECDIG PRI_DECDIG_PREFIX"d" 41 | #define PRIiDECDIG PRI_DECDIG_PREFIX"i" 42 | #define PRIoDECDIG PRI_DECDIG_PREFIX"o" 43 | #define PRIuDECDIG PRI_DECDIG_PREFIX"u" 44 | #define PRIxDECDIG PRI_DECDIG_PREFIX"x" 45 | #define PRIXDECDIG PRI_DECDIG_PREFIX"X" 46 | 47 | #define PRIdDECDIG_DBL PRI_DECDIG_DBL_PREFIX"d" 48 | #define PRIiDECDIG_DBL PRI_DECDIG_DBL_PREFIX"i" 49 | #define PRIoDECDIG_DBL PRI_DECDIG_DBL_PREFIX"o" 50 | #define PRIuDECDIG_DBL PRI_DECDIG_DBL_PREFIX"u" 51 | #define PRIxDECDIG_DBL PRI_DECDIG_DBL_PREFIX"x" 52 | #define PRIXDECDIG_DBL PRI_DECDIG_DBL_PREFIX"X" 53 | 54 | #if SIZEOF_DECDIG == 4 55 | # define BIGDECIMAL_BASE ((DECDIG)1000000000U) 56 | # define BIGDECIMAL_COMPONENT_FIGURES 9 57 | /* 58 | * The number of components required for a 64-bit integer. 59 | * 60 | * INT64_MAX: 9_223372036_854775807 61 | * UINT64_MAX: 18_446744073_709551615 62 | */ 63 | # define BIGDECIMAL_INT64_MAX_LENGTH 3 64 | 65 | #elif SIZEOF_DECDIG == 2 66 | # define BIGDECIMAL_BASE ((DECDIG)10000U) 67 | # define BIGDECIMAL_COMPONENT_FIGURES 4 68 | /* 69 | * The number of components required for a 64-bit integer. 70 | * 71 | * INT64_MAX: 922_3372_0368_5477_5807 72 | * UINT64_MAX: 1844_6744_0737_0955_1615 73 | */ 74 | # define BIGDECIMAL_INT64_MAX_LENGTH 5 75 | 76 | #else 77 | # error Unknown size of DECDIG 78 | #endif 79 | 80 | #define BIGDECIMAL_DOUBLE_FIGURES (1+DBL_DIG) 81 | 82 | #if defined(__cplusplus) 83 | extern "C" { 84 | #if 0 85 | } /* satisfy cc-mode */ 86 | #endif 87 | #endif 88 | 89 | extern VALUE rb_cBigDecimal; 90 | 91 | /* 92 | * NaN & Infinity 93 | */ 94 | #define SZ_NaN "NaN" 95 | #define SZ_INF "Infinity" 96 | #define SZ_PINF "+Infinity" 97 | #define SZ_NINF "-Infinity" 98 | 99 | /* 100 | * #define VP_EXPORT other than static to let VP_ routines 101 | * be called from outside of this module. 102 | */ 103 | #define VP_EXPORT static 104 | 105 | /* Exception mode */ 106 | #define VP_EXCEPTION_ALL ((unsigned short)0x00FF) 107 | #define VP_EXCEPTION_INFINITY ((unsigned short)0x0001) 108 | #define VP_EXCEPTION_NaN ((unsigned short)0x0002) 109 | #define VP_EXCEPTION_UNDERFLOW ((unsigned short)0x0004) 110 | #define VP_EXCEPTION_OVERFLOW ((unsigned short)0x0001) /* 0x0008) */ 111 | #define VP_EXCEPTION_ZERODIVIDE ((unsigned short)0x0010) 112 | 113 | /* Following 2 exceptions can't controlled by user */ 114 | #define VP_EXCEPTION_OP ((unsigned short)0x0020) 115 | 116 | #define BIGDECIMAL_EXCEPTION_MODE_DEFAULT 0U 117 | 118 | /* This is used in BigDecimal#mode */ 119 | #define VP_ROUND_MODE ((unsigned short)0x0100) 120 | 121 | /* Rounding mode */ 122 | #define VP_ROUND_UP RBD_ROUND_UP 123 | #define VP_ROUND_DOWN RBD_ROUND_DOWN 124 | #define VP_ROUND_HALF_UP RBD_ROUND_HALF_UP 125 | #define VP_ROUND_HALF_DOWN RBD_ROUND_HALF_DOWN 126 | #define VP_ROUND_CEIL RBD_ROUND_CEIL 127 | #define VP_ROUND_FLOOR RBD_ROUND_FLOOR 128 | #define VP_ROUND_HALF_EVEN RBD_ROUND_HALF_EVEN 129 | 130 | enum rbd_rounding_mode { 131 | RBD_ROUND_UP = 1, 132 | RBD_ROUND_DOWN = 2, 133 | RBD_ROUND_HALF_UP = 3, 134 | RBD_ROUND_HALF_DOWN = 4, 135 | RBD_ROUND_CEIL = 5, 136 | RBD_ROUND_FLOOR = 6, 137 | RBD_ROUND_HALF_EVEN = 7, 138 | 139 | RBD_ROUND_DEFAULT = RBD_ROUND_HALF_UP, 140 | RBD_ROUND_TRUNCATE = RBD_ROUND_DOWN, 141 | RBD_ROUND_BANKER = RBD_ROUND_HALF_EVEN, 142 | RBD_ROUND_CEILING = RBD_ROUND_CEIL 143 | }; 144 | 145 | #define BIGDECIMAL_ROUNDING_MODE_DEFAULT VP_ROUND_HALF_UP 146 | 147 | /* Sign flag */ 148 | #define VP_SIGN_NaN 0 /* NaN */ 149 | #define VP_SIGN_POSITIVE_ZERO 1 /* Positive zero */ 150 | #define VP_SIGN_NEGATIVE_ZERO -1 /* Negative zero */ 151 | #define VP_SIGN_POSITIVE_FINITE 2 /* Positive finite number */ 152 | #define VP_SIGN_NEGATIVE_FINITE -2 /* Negative finite number */ 153 | #define VP_SIGN_POSITIVE_INFINITE 3 /* Positive infinite number */ 154 | #define VP_SIGN_NEGATIVE_INFINITE -3 /* Negative infinite number */ 155 | 156 | /* The size of fraction part array */ 157 | #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) 158 | #define FLEXIBLE_ARRAY_SIZE /* */ 159 | #elif defined(__GNUC__) && !defined(__STRICT_ANSI__) 160 | #define FLEXIBLE_ARRAY_SIZE 0 161 | #else 162 | #define FLEXIBLE_ARRAY_SIZE 1 163 | #endif 164 | 165 | /* 166 | * VP representation 167 | * r = 0.xxxxxxxxx *BASE**exponent 168 | */ 169 | typedef struct { 170 | size_t MaxPrec; /* Maximum precision size */ 171 | /* This is the actual size of frac[] */ 172 | /*(frac[0] to frac[MaxPrec] are available). */ 173 | size_t Prec; /* Current precision size. */ 174 | /* This indicates how much the */ 175 | /* array frac[] is actually used. */ 176 | SIGNED_VALUE exponent; /* Exponent part. */ 177 | short sign; /* Attributes of the value. */ 178 | /* 179 | * ==0 : NaN 180 | * 1 : Positive zero 181 | * -1 : Negative zero 182 | * 2 : Positive number 183 | * -2 : Negative number 184 | * 3 : Positive infinite number 185 | * -3 : Negative infinite number 186 | */ 187 | short flag; /* Not used in vp_routines,space for user. */ 188 | DECDIG frac[FLEXIBLE_ARRAY_SIZE]; /* Array of fraction part. */ 189 | } Real; 190 | 191 | /* 192 | * ------------------ 193 | * EXPORTables. 194 | * ------------------ 195 | */ 196 | 197 | #define VpBaseFig() BIGDECIMAL_COMPONENT_FIGURES 198 | 199 | /* Zero,Inf,NaN (isinf(),isnan() used to check) */ 200 | VP_EXPORT double VpGetDoubleNaN(void); 201 | VP_EXPORT double VpGetDoublePosInf(void); 202 | VP_EXPORT double VpGetDoubleNegInf(void); 203 | VP_EXPORT double VpGetDoubleNegZero(void); 204 | 205 | /* These 2 functions added at v1.1.7 */ 206 | VP_EXPORT size_t VpGetPrecLimit(void); 207 | VP_EXPORT void VpSetPrecLimit(size_t n); 208 | 209 | /* Round mode */ 210 | VP_EXPORT int VpIsRoundMode(unsigned short n); 211 | VP_EXPORT unsigned short VpGetRoundMode(void); 212 | VP_EXPORT unsigned short VpSetRoundMode(unsigned short n); 213 | 214 | VP_EXPORT int VpException(unsigned short f,const char *str,int always); 215 | VP_EXPORT size_t VpNumOfChars(Real *vp,const char *pszFmt); 216 | VP_EXPORT size_t VpInit(DECDIG BaseVal); 217 | VP_EXPORT Real *VpAlloc(const char *szVal, int strict_p, int exc); 218 | VP_EXPORT size_t VpAsgn(Real *c, Real *a, int isw); 219 | VP_EXPORT size_t VpAddSub(Real *c,Real *a,Real *b,int operation); 220 | VP_EXPORT size_t VpMult(Real *c,Real *a,Real *b); 221 | VP_EXPORT size_t VpDivd(Real *c,Real *r,Real *a,Real *b); 222 | VP_EXPORT int VpNmlz(Real *a); 223 | VP_EXPORT int VpComp(Real *a,Real *b); 224 | VP_EXPORT ssize_t VpExponent10(Real *a); 225 | VP_EXPORT void VpSzMantissa(Real *a, char *buf, size_t bufsize); 226 | VP_EXPORT int VpToSpecialString(Real *a, char *buf, size_t bufsize, int fPlus); 227 | VP_EXPORT void VpToString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); 228 | VP_EXPORT void VpToFString(Real *a, char *buf, size_t bufsize, size_t fFmt, int fPlus); 229 | VP_EXPORT int VpCtoV(Real *a, const char *int_chr, size_t ni, const char *frac, size_t nf, const char *exp_chr, size_t ne); 230 | VP_EXPORT int VpVtoD(double *d, SIGNED_VALUE *e, Real *m); 231 | VP_EXPORT int VpActiveRound(Real *y, Real *x, unsigned short f, ssize_t il); 232 | VP_EXPORT int VpMidRound(Real *y, unsigned short f, ssize_t nf); 233 | VP_EXPORT int VpLeftRound(Real *y, unsigned short f, ssize_t nf); 234 | VP_EXPORT void VpFrac(Real *y, Real *x); 235 | 236 | /* VP constants */ 237 | VP_EXPORT Real *VpOne(void); 238 | 239 | /* 240 | * ------------------ 241 | * MACRO definitions. 242 | * ------------------ 243 | */ 244 | #define Abs(a) (((a)>= 0)?(a):(-(a))) 245 | #define Max(a, b) (((a)>(b))?(a):(b)) 246 | #define Min(a, b) (((a)>(b))?(b):(a)) 247 | 248 | /* Sign */ 249 | 250 | /* VpGetSign(a) returns 1,-1 if a>0,a<0 respectively */ 251 | #define VpGetSign(a) (((a)->sign>0)?1:(-1)) 252 | /* Change sign of a to a>0,a<0 if s = 1,-1 respectively */ 253 | #define VpChangeSign(a,s) {if((s)>0) (a)->sign=(short)Abs((ssize_t)(a)->sign);else (a)->sign=-(short)Abs((ssize_t)(a)->sign);} 254 | /* Sets sign of a to a>0,a<0 if s = 1,-1 respectively */ 255 | #define VpSetSign(a,s) {if((s)>0) (a)->sign=(short)VP_SIGN_POSITIVE_FINITE;else (a)->sign=(short)VP_SIGN_NEGATIVE_FINITE;} 256 | 257 | /* 1 */ 258 | #define VpSetOne(a) {(a)->Prec=(a)->exponent=(a)->frac[0]=1;(a)->sign=VP_SIGN_POSITIVE_FINITE;} 259 | 260 | /* ZEROs */ 261 | #define VpIsPosZero(a) ((a)->sign==VP_SIGN_POSITIVE_ZERO) 262 | #define VpIsNegZero(a) ((a)->sign==VP_SIGN_NEGATIVE_ZERO) 263 | #define VpIsZero(a) (VpIsPosZero(a) || VpIsNegZero(a)) 264 | #define VpSetPosZero(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_POSITIVE_ZERO) 265 | #define VpSetNegZero(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NEGATIVE_ZERO) 266 | #define VpSetZero(a,s) (void)(((s)>0)?VpSetPosZero(a):VpSetNegZero(a)) 267 | 268 | /* NaN */ 269 | #define VpIsNaN(a) ((a)->sign==VP_SIGN_NaN) 270 | #define VpSetNaN(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NaN) 271 | 272 | /* Infinity */ 273 | #define VpIsPosInf(a) ((a)->sign==VP_SIGN_POSITIVE_INFINITE) 274 | #define VpIsNegInf(a) ((a)->sign==VP_SIGN_NEGATIVE_INFINITE) 275 | #define VpIsInf(a) (VpIsPosInf(a) || VpIsNegInf(a)) 276 | #define VpIsDef(a) ( !(VpIsNaN(a)||VpIsInf(a)) ) 277 | #define VpSetPosInf(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_POSITIVE_INFINITE) 278 | #define VpSetNegInf(a) ((a)->frac[0]=0,(a)->Prec=1,(a)->sign=VP_SIGN_NEGATIVE_INFINITE) 279 | #define VpSetInf(a,s) (void)(((s)>0)?VpSetPosInf(a):VpSetNegInf(a)) 280 | #define VpHasVal(a) (a->frac[0]) 281 | #define VpIsOne(a) ((a->Prec==1)&&(a->frac[0]==1)&&(a->exponent==1)) 282 | #ifdef BIGDECIMAL_DEBUG 283 | int VpVarCheck(Real * v); 284 | #endif /* BIGDECIMAL_DEBUG */ 285 | 286 | #if defined(__cplusplus) 287 | #if 0 288 | { /* satisfy cc-mode */ 289 | #endif 290 | } /* extern "C" { */ 291 | #endif 292 | #endif /* RUBY_BIG_DECIMAL_H */ 293 | -------------------------------------------------------------------------------- /lib/bigdecimal.rb: -------------------------------------------------------------------------------- 1 | if RUBY_ENGINE == 'jruby' 2 | JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary") 3 | 4 | class BigDecimal 5 | def _decimal_shift(i) # :nodoc: 6 | to_java.move_point_right(i).to_d 7 | end 8 | end 9 | else 10 | require 'bigdecimal.so' 11 | end 12 | 13 | class BigDecimal 14 | module Internal # :nodoc: 15 | 16 | # Coerce x to BigDecimal with the specified precision. 17 | # TODO: some methods (example: BigMath.exp) require more precision than specified to coerce. 18 | def self.coerce_to_bigdecimal(x, prec, method_name) # :nodoc: 19 | case x 20 | when BigDecimal 21 | return x 22 | when Integer, Float 23 | return BigDecimal(x, 0) 24 | when Rational 25 | return BigDecimal(x, [prec, 2 * BigDecimal.double_fig].max) 26 | end 27 | raise ArgumentError, "#{x.inspect} can't be coerced into BigDecimal" 28 | end 29 | 30 | def self.coerce_validate_prec(prec, method_name, accept_zero: false) # :nodoc: 31 | unless Integer === prec 32 | original = prec 33 | # Emulate Integer.try_convert for ruby < 3.1 34 | if prec.respond_to?(:to_int) 35 | prec = prec.to_int 36 | else 37 | raise TypeError, "no implicit conversion of #{original.class} into Integer" 38 | end 39 | raise TypeError, "can't convert #{original.class} to Integer" unless Integer === prec 40 | end 41 | 42 | if accept_zero 43 | raise ArgumentError, "Negative precision for #{method_name}" if prec < 0 44 | else 45 | raise ArgumentError, "Zero or negative precision for #{method_name}" if prec <= 0 46 | end 47 | prec 48 | end 49 | 50 | def self.infinity_computation_result # :nodoc: 51 | if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_INFINITY) 52 | raise FloatDomainError, "Computation results in 'Infinity'" 53 | end 54 | BigDecimal::INFINITY 55 | end 56 | 57 | def self.nan_computation_result # :nodoc: 58 | if BigDecimal.mode(BigDecimal::EXCEPTION_ALL).anybits?(BigDecimal::EXCEPTION_NaN) 59 | raise FloatDomainError, "Computation results to 'NaN'" 60 | end 61 | BigDecimal::NAN 62 | end 63 | end 64 | 65 | # call-seq: 66 | # self ** other -> bigdecimal 67 | # 68 | # Returns the \BigDecimal value of +self+ raised to power +other+: 69 | # 70 | # b = BigDecimal('3.14') 71 | # b ** 2 # => 0.98596e1 72 | # b ** 2.0 # => 0.98596e1 73 | # b ** Rational(2, 1) # => 0.98596e1 74 | # 75 | # Related: BigDecimal#power. 76 | # 77 | def **(y) 78 | case y 79 | when BigDecimal, Integer, Float, Rational 80 | power(y) 81 | when nil 82 | raise TypeError, 'wrong argument type NilClass' 83 | else 84 | x, y = y.coerce(self) 85 | x**y 86 | end 87 | end 88 | 89 | # call-seq: 90 | # power(n) 91 | # power(n, prec) 92 | # 93 | # Returns the value raised to the power of n. 94 | # 95 | # Also available as the operator **. 96 | # 97 | def power(y, prec = 0) 98 | prec = Internal.coerce_validate_prec(prec, :power, accept_zero: true) 99 | x = self 100 | y = Internal.coerce_to_bigdecimal(y, prec.nonzero? || n_significant_digits, :power) 101 | 102 | return Internal.nan_computation_result if x.nan? || y.nan? 103 | return BigDecimal(1) if y.zero? 104 | 105 | if y.infinite? 106 | if x < 0 107 | return BigDecimal(0) if x < -1 && y.negative? 108 | return BigDecimal(0) if x > -1 && y.positive? 109 | raise Math::DomainError, 'Result undefined for negative base raised to infinite power' 110 | elsif x < 1 111 | return y.positive? ? BigDecimal(0) : BigDecimal::Internal.infinity_computation_result 112 | elsif x == 1 113 | return BigDecimal(1) 114 | else 115 | return y.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) 116 | end 117 | end 118 | 119 | if x.infinite? && y < 0 120 | # Computation result will be +0 or -0. Avoid overflow. 121 | neg = x < 0 && y.frac.zero? && y % 2 == 1 122 | return neg ? -BigDecimal(0) : BigDecimal(0) 123 | end 124 | 125 | if x.zero? 126 | return BigDecimal(1) if y.zero? 127 | return BigDecimal(0) if y > 0 128 | if y.frac.zero? && y % 2 == 1 && x.sign == -1 129 | return -BigDecimal::Internal.infinity_computation_result 130 | else 131 | return BigDecimal::Internal.infinity_computation_result 132 | end 133 | elsif x < 0 134 | if y.frac.zero? 135 | if y % 2 == 0 136 | return (-x).power(y, prec) 137 | else 138 | return -(-x).power(y, prec) 139 | end 140 | else 141 | raise Math::DomainError, 'Computation results in complex number' 142 | end 143 | elsif x == 1 144 | return BigDecimal(1) 145 | end 146 | 147 | limit = BigDecimal.limit 148 | frac_part = y.frac 149 | 150 | if frac_part.zero? && prec.zero? && limit.zero? 151 | # Infinite precision calculation for `x ** int` and `x.power(int)` 152 | int_part = y.fix.to_i 153 | int_part = -int_part if (neg = int_part < 0) 154 | ans = BigDecimal(1) 155 | n = 1 156 | xn = x 157 | while true 158 | ans *= xn if int_part.allbits?(n) 159 | n <<= 1 160 | break if n > int_part 161 | xn *= xn 162 | # Detect overflow/underflow before consuming infinite memory 163 | if (xn.exponent.abs - 1) * int_part / n >= 0x7FFFFFFFFFFFFFFF 164 | return ((xn.exponent > 0) ^ neg ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0)) * (int_part.even? || x > 0 ? 1 : -1) 165 | end 166 | end 167 | return neg ? BigDecimal(1) / ans : ans 168 | end 169 | 170 | result_prec = prec.nonzero? || [x.n_significant_digits, y.n_significant_digits, BigDecimal.double_fig].max + BigDecimal.double_fig 171 | result_prec = [result_prec, limit].min if prec.zero? && limit.nonzero? 172 | 173 | prec2 = result_prec + BigDecimal.double_fig 174 | 175 | if y < 0 176 | inv = x.power(-y, prec2) 177 | return BigDecimal(0) if inv.infinite? 178 | return BigDecimal::Internal.infinity_computation_result if inv.zero? 179 | return BigDecimal(1).div(inv, result_prec) 180 | end 181 | 182 | if frac_part.zero? && y.exponent < Math.log(result_prec) * 5 + 20 183 | # Use exponentiation by squaring if y is an integer and not too large 184 | pow_prec = prec2 + y.exponent 185 | n = 1 186 | xn = x 187 | ans = BigDecimal(1) 188 | int_part = y.fix.to_i 189 | while true 190 | ans = ans.mult(xn, pow_prec) if int_part.allbits?(n) 191 | n <<= 1 192 | break if n > int_part 193 | xn = xn.mult(xn, pow_prec) 194 | end 195 | ans.mult(1, result_prec) 196 | else 197 | if x > 1 && x.finite? 198 | # To calculate exp(z, prec), z needs prec+max(z.exponent, 0) precision if z > 0. 199 | # Estimate (y*log(x)).exponent 200 | logx_exponent = x < 2 ? (x - 1).exponent : Math.log10(x.exponent).round 201 | ylogx_exponent = y.exponent + logx_exponent 202 | prec2 += [ylogx_exponent, 0].max 203 | end 204 | BigMath.exp(BigMath.log(x, prec2).mult(y, prec2), result_prec) 205 | end 206 | end 207 | 208 | # Returns the square root of the value. 209 | # 210 | # Result has at least prec significant digits. 211 | # 212 | def sqrt(prec) 213 | prec = Internal.coerce_validate_prec(prec, :sqrt, accept_zero: true) 214 | return Internal.infinity_computation_result if infinite? == 1 215 | 216 | raise FloatDomainError, 'sqrt of negative value' if self < 0 217 | raise FloatDomainError, "sqrt of 'NaN'(Not a Number)" if nan? 218 | return self if zero? 219 | 220 | if prec == 0 221 | limit = BigDecimal.limit 222 | prec = n_significant_digits + BigDecimal.double_fig 223 | prec = [limit, prec].min if limit.nonzero? 224 | end 225 | 226 | ex = exponent / 2 227 | x = _decimal_shift(-2 * ex) 228 | y = BigDecimal(Math.sqrt(x.to_f), 0) 229 | precs = [prec + BigDecimal.double_fig] 230 | precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig 231 | precs.reverse_each do |p| 232 | y = y.add(x.div(y, p), p).div(2, p) 233 | end 234 | y._decimal_shift(ex).mult(1, prec) 235 | end 236 | end 237 | 238 | # Core BigMath methods for BigDecimal (log, exp) are defined here. 239 | # Other methods (sin, cos, atan) are defined in 'bigdecimal/math.rb'. 240 | module BigMath 241 | module_function 242 | 243 | # call-seq: 244 | # BigMath.log(decimal, numeric) -> BigDecimal 245 | # 246 | # Computes the natural logarithm of +decimal+ to the specified number of 247 | # digits of precision, +numeric+. 248 | # 249 | # If +decimal+ is zero or negative, raises Math::DomainError. 250 | # 251 | # If +decimal+ is positive infinity, returns Infinity. 252 | # 253 | # If +decimal+ is NaN, returns NaN. 254 | # 255 | def log(x, prec) 256 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :log) 257 | raise Math::DomainError, 'Complex argument for BigMath.log' if Complex === x 258 | 259 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log) 260 | return BigDecimal::Internal.nan_computation_result if x.nan? 261 | raise Math::DomainError, 'Negative argument for log' if x < 0 262 | return -BigDecimal::Internal.infinity_computation_result if x.zero? 263 | return BigDecimal::Internal.infinity_computation_result if x.infinite? 264 | return BigDecimal(0) if x == 1 265 | 266 | prec2 = prec + BigDecimal.double_fig 267 | BigDecimal.save_limit do 268 | BigDecimal.limit(0) 269 | if x > 10 || x < 0.1 270 | log10 = log(BigDecimal(10), prec2) 271 | exponent = x.exponent 272 | x = x._decimal_shift(-exponent) 273 | if x < 0.3 274 | x *= 10 275 | exponent -= 1 276 | end 277 | return (log10 * exponent).add(log(x, prec2), prec) 278 | end 279 | 280 | x_minus_one_exponent = (x - 1).exponent 281 | 282 | # log(x) = log(sqrt(sqrt(sqrt(sqrt(x))))) * 2**sqrt_steps 283 | sqrt_steps = [Integer.sqrt(prec2) + 3 * x_minus_one_exponent, 0].max 284 | 285 | lg2 = 0.3010299956639812 286 | sqrt_prec = prec2 + [-x_minus_one_exponent, 0].max + (sqrt_steps * lg2).ceil 287 | 288 | sqrt_steps.times do 289 | x = x.sqrt(sqrt_prec) 290 | end 291 | 292 | # Taylor series for log(x) around 1 293 | # log(x) = -log((1 + X) / (1 - X)) where X = (x - 1) / (x + 1) 294 | # log(x) = 2 * (X + X**3 / 3 + X**5 / 5 + X**7 / 7 + ...) 295 | x = (x - 1).div(x + 1, sqrt_prec) 296 | y = x 297 | x2 = x.mult(x, prec2) 298 | 1.step do |i| 299 | n = prec2 + x.exponent - y.exponent + x2.exponent 300 | break if n <= 0 || x.zero? 301 | x = x.mult(x2.round(n - x2.exponent), n) 302 | y = y.add(x.div(2 * i + 1, n), prec2) 303 | end 304 | 305 | y.mult(2 ** (sqrt_steps + 1), prec) 306 | end 307 | end 308 | 309 | # Taylor series for exp(x) around 0 310 | private_class_method def _exp_taylor(x, prec) # :nodoc: 311 | xn = BigDecimal(1) 312 | y = BigDecimal(1) 313 | 1.step do |i| 314 | n = prec + xn.exponent 315 | break if n <= 0 || xn.zero? 316 | xn = xn.mult(x, n).div(i, n) 317 | y = y.add(xn, prec) 318 | end 319 | y 320 | end 321 | 322 | # call-seq: 323 | # BigMath.exp(decimal, numeric) -> BigDecimal 324 | # 325 | # Computes the value of e (the base of natural logarithms) raised to the 326 | # power of +decimal+, to the specified number of digits of precision. 327 | # 328 | # If +decimal+ is infinity, returns Infinity. 329 | # 330 | # If +decimal+ is NaN, returns NaN. 331 | # 332 | def exp(x, prec) 333 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :exp) 334 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :exp) 335 | return BigDecimal::Internal.nan_computation_result if x.nan? 336 | return x.positive? ? BigDecimal::Internal.infinity_computation_result : BigDecimal(0) if x.infinite? 337 | return BigDecimal(1) if x.zero? 338 | 339 | # exp(x * 10**cnt) = exp(x)**(10**cnt) 340 | cnt = x < -1 || x > 1 ? x.exponent : 0 341 | prec2 = prec + BigDecimal.double_fig + cnt 342 | x = x._decimal_shift(-cnt) 343 | 344 | # Calculation of exp(small_prec) is fast because calculation of x**n is fast 345 | # Calculation of exp(small_abs) converges fast. 346 | # exp(x) = exp(small_prec_part + small_abs_part) = exp(small_prec_part) * exp(small_abs_part) 347 | x_small_prec = x.round(Integer.sqrt(prec2)) 348 | y = _exp_taylor(x_small_prec, prec2).mult(_exp_taylor(x.sub(x_small_prec, prec2), prec2), prec2) 349 | 350 | # calculate exp(x * 10**cnt) from exp(x) 351 | # exp(x * 10**k) = exp(x * 10**(k - 1)) ** 10 352 | cnt.times do 353 | y2 = y.mult(y, prec2) 354 | y5 = y2.mult(y2, prec2).mult(y, prec2) 355 | y = y5.mult(y5, prec2) 356 | end 357 | 358 | y.mult(1, prec) 359 | end 360 | end 361 | -------------------------------------------------------------------------------- /test/bigdecimal/test_bigmath.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require_relative "helper" 3 | require "bigdecimal/math" 4 | 5 | class TestBigMath < Test::Unit::TestCase 6 | include TestBigDecimalBase 7 | include BigMath 8 | N = 20 9 | # SQRT in 116 (= 100 + double_fig) digits 10 | SQRT2 = BigDecimal("1.4142135623730950488016887242096980785696718753769480731766797379907324784621070388503875343276415727350138462309123") 11 | SQRT3 = BigDecimal("1.7320508075688772935274463415058723669428052538103806280558069794519330169088000370811461867572485756756261414154067") 12 | SQRT5 = BigDecimal("2.2360679774997896964091736687312762354406183596115257242708972454105209256378048994144144083787822749695081761507738") 13 | PINF = BigDecimal("+Infinity") 14 | MINF = BigDecimal("-Infinity") 15 | NAN = BigDecimal("NaN") 16 | 17 | def test_pi 18 | assert_equal( 19 | BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068"), 20 | PI(100) 21 | ) 22 | assert_converge_in_precision {|n| PI(n) } 23 | end 24 | 25 | def test_e 26 | assert_equal( 27 | BigDecimal("2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427"), 28 | E(100) 29 | ) 30 | assert_converge_in_precision {|n| E(n) } 31 | end 32 | 33 | def assert_consistent_precision_acceptance(accept_zero: false) 34 | value = yield 5 35 | assert_equal(value, yield(5.9)) 36 | 37 | obj_with_to_int = Object.new 38 | obj_with_to_int.define_singleton_method(:to_int) { 5 } 39 | assert_equal(value, yield(obj_with_to_int)) 40 | 41 | wrong_to_int = Object.new 42 | wrong_to_int.define_singleton_method(:to_int) { 5.5 } 43 | assert_raise(TypeError) { yield wrong_to_int } 44 | 45 | assert_raise(TypeError) { yield nil } 46 | assert_raise(TypeError) { yield '5' } 47 | assert_raise(ArgumentError) { yield(-1) } 48 | if accept_zero 49 | assert_nothing_raised { yield 0 } 50 | else 51 | assert_raise(ArgumentError) { yield 0 } 52 | end 53 | end 54 | 55 | def test_consistent_precision_acceptance 56 | x = BigDecimal('0.123456789') 57 | # Exclude div because div(x, nil) is a special case 58 | assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.add(x, prec) } 59 | assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sub(x, prec) } 60 | assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.mult(x, prec) } 61 | assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.power(x, prec) } 62 | assert_consistent_precision_acceptance(accept_zero: true) {|prec| x.sqrt(prec) } 63 | assert_consistent_precision_acceptance {|prec| BigMath.exp(x, prec) } 64 | assert_consistent_precision_acceptance {|prec| BigMath.log(x, prec) } 65 | assert_consistent_precision_acceptance {|prec| BigMath.sqrt(x, prec) } 66 | assert_consistent_precision_acceptance {|prec| BigMath.cbrt(x, prec) } 67 | assert_consistent_precision_acceptance {|prec| BigMath.hypot(x, x + 1, prec) } 68 | assert_consistent_precision_acceptance {|prec| BigMath.sin(x, prec) } 69 | assert_consistent_precision_acceptance {|prec| BigMath.cos(x, prec) } 70 | assert_consistent_precision_acceptance {|prec| BigMath.tan(x, prec) } 71 | assert_consistent_precision_acceptance {|prec| BigMath.asin(x, prec) } 72 | assert_consistent_precision_acceptance {|prec| BigMath.acos(x, prec) } 73 | assert_consistent_precision_acceptance {|prec| BigMath.atan(x, prec) } 74 | assert_consistent_precision_acceptance {|prec| BigMath.atan2(x, x + 1, prec) } 75 | assert_consistent_precision_acceptance {|prec| BigMath.sinh(x, prec) } 76 | assert_consistent_precision_acceptance {|prec| BigMath.cosh(x, prec) } 77 | assert_consistent_precision_acceptance {|prec| BigMath.tanh(x, prec) } 78 | assert_consistent_precision_acceptance {|prec| BigMath.asinh(x, prec) } 79 | assert_consistent_precision_acceptance {|prec| BigMath.acosh(x + 1, prec) } 80 | assert_consistent_precision_acceptance {|prec| BigMath.atanh(x, prec) } 81 | assert_consistent_precision_acceptance {|prec| BigMath.log2(x, prec) } 82 | assert_consistent_precision_acceptance {|prec| BigMath.log10(x, prec) } 83 | assert_consistent_precision_acceptance {|prec| BigMath.log1p(x, prec) } 84 | assert_consistent_precision_acceptance {|prec| BigMath.expm1(x, prec) } 85 | assert_consistent_precision_acceptance {|prec| BigMath.erf(x, prec) } 86 | assert_consistent_precision_acceptance {|prec| BigMath.erfc(x, prec) } 87 | assert_consistent_precision_acceptance {|prec| BigMath.gamma(x, prec) } 88 | assert_consistent_precision_acceptance {|prec| BigMath.lgamma(x, prec) } 89 | 90 | assert_consistent_precision_acceptance {|prec| BigMath.E(prec) } 91 | assert_consistent_precision_acceptance {|prec| BigMath.PI(prec) } 92 | end 93 | 94 | def test_coerce_argument 95 | f = 0.8 96 | bd = BigDecimal(f) 97 | assert_equal(exp(bd, N), exp(f, N)) 98 | assert_equal(log(bd, N), log(f, N)) 99 | assert_equal(sqrt(bd, N), sqrt(f, N)) 100 | assert_equal(cbrt(bd, N), cbrt(f, N)) 101 | assert_equal(hypot(bd, bd, N), hypot(f, f, N)) 102 | assert_equal(sin(bd, N), sin(f, N)) 103 | assert_equal(cos(bd, N), cos(f, N)) 104 | assert_equal(tan(bd, N), tan(f, N)) 105 | assert_equal(asin(bd, N), asin(f, N)) 106 | assert_equal(acos(bd, N), acos(f, N)) 107 | assert_equal(atan(bd, N), atan(f, N)) 108 | assert_equal(atan2(bd, bd, N), atan2(f, f, N)) 109 | assert_equal(sinh(bd, N), sinh(f, N)) 110 | assert_equal(cosh(bd, N), cosh(f, N)) 111 | assert_equal(tanh(bd, N), tanh(f, N)) 112 | assert_equal(asinh(bd, N), asinh(f, N)) 113 | assert_equal(acosh(bd * 2, N), acosh(f * 2, N)) 114 | assert_equal(atanh(bd, N), atanh(f, N)) 115 | assert_equal(log2(bd, N), log2(f, N)) 116 | assert_equal(log10(bd, N), log10(f, N)) 117 | assert_equal(log1p(bd, N), log1p(f, N)) 118 | assert_equal(expm1(bd, N), expm1(f, N)) 119 | assert_equal(erf(bd, N), erf(f, N)) 120 | assert_equal(erfc(bd, N), erfc(f, N)) 121 | assert_equal(gamma(bd, N), gamma(f, N)) 122 | assert_equal(lgamma(bd, N), lgamma(f, N)) 123 | end 124 | 125 | def test_sqrt 126 | assert_in_delta(2**0.5, sqrt(BigDecimal("2"), N)) 127 | assert_equal(10, sqrt(BigDecimal("100"), N)) 128 | assert_equal(0.0, sqrt(BigDecimal("0"), N)) 129 | assert_equal(0.0, sqrt(BigDecimal("-0"), N)) 130 | assert_raise(FloatDomainError) {sqrt(BigDecimal("-1.0"), N)} 131 | assert_raise(FloatDomainError) {sqrt(NAN, N)} 132 | assert_raise(FloatDomainError) {sqrt(PINF, N)} 133 | assert_in_exact_precision(SQRT2, sqrt(BigDecimal("2"), 100), 100) 134 | assert_in_exact_precision(SQRT3, sqrt(BigDecimal("3"), 100), 100) 135 | assert_converge_in_precision {|n| sqrt(BigDecimal("2"), n) } 136 | assert_converge_in_precision {|n| sqrt(BigDecimal("2e-50"), n) } 137 | assert_converge_in_precision {|n| sqrt(BigDecimal("2e50"), n) } 138 | end 139 | 140 | def test_cbrt 141 | assert_equal(1234, cbrt(BigDecimal(1234**3), N)) 142 | assert_equal(-12345, cbrt(BigDecimal(-12345**3), N)) 143 | assert_equal(12345678987654321, cbrt(BigDecimal(12345678987654321) ** 3, N)) 144 | assert_equal(0, cbrt(BigDecimal("0"), N)) 145 | assert_equal(0, cbrt(BigDecimal("-0"), N)) 146 | assert_positive_infinite_calculation { cbrt(PINF, N) } 147 | assert_negative_infinite_calculation { cbrt(MINF, N) } 148 | 149 | assert_in_exact_precision(SQRT2, cbrt(SQRT2 ** 3, 100), 100) 150 | assert_in_exact_precision(SQRT3, cbrt(SQRT3 ** 3, 100), 100) 151 | assert_equal(BigDecimal("3e50"), cbrt(BigDecimal("27e150"), N)) 152 | assert_equal(BigDecimal("-4e50"), cbrt(BigDecimal("-64e150"), N)) 153 | assert_in_epsilon(Math.cbrt(28e150), cbrt(BigDecimal("28e150"), N)) 154 | assert_in_epsilon(Math.cbrt(27e151), cbrt(BigDecimal("27e151"), N)) 155 | assert_converge_in_precision {|n| cbrt(BigDecimal("2"), n) } 156 | assert_converge_in_precision {|n| cbrt(BigDecimal("2e-50"), n) } 157 | assert_converge_in_precision {|n| cbrt(SQRT2, n) } 158 | assert_converge_in_precision {|n| cbrt(BigDecimal("2e50"), n) } 159 | end 160 | 161 | def test_hypot 162 | assert_in_exact_precision(SQRT2, hypot(BigDecimal("1"), BigDecimal("1"), 100), 100) 163 | assert_in_exact_precision(SQRT5, hypot(SQRT2, SQRT3, 100), 100) 164 | assert_equal(0, hypot(BigDecimal(0), BigDecimal(0), N)) 165 | assert_positive_infinite_calculation { hypot(PINF, SQRT3, N) } 166 | assert_positive_infinite_calculation { hypot(SQRT3, MINF, N) } 167 | assert_converge_in_precision {|n| hypot(BigDecimal("1e-30"), BigDecimal("2e-30"), n) } 168 | assert_converge_in_precision {|n| hypot(BigDecimal("1.23"), BigDecimal("4.56"), n) } 169 | assert_converge_in_precision {|n| hypot(SQRT2 - 1, SQRT3 - 1, n) } 170 | assert_converge_in_precision {|n| hypot(BigDecimal("2e30"), BigDecimal("1e30"), n) } 171 | end 172 | 173 | def test_sin 174 | assert_in_delta(0.0, sin(BigDecimal("0.0"), N)) 175 | assert_in_delta(Math.sqrt(2.0) / 2, sin(PI(N) / 4, N)) 176 | assert_in_delta(1.0, sin(PI(N) / 2, N)) 177 | assert_in_delta(0.0, sin(PI(N) * 2, N)) 178 | assert_in_delta(0.0, sin(PI(N), N)) 179 | assert_in_delta(-1.0, sin(PI(N) / -2, N)) 180 | assert_in_delta(0.0, sin(PI(N) * -2, N)) 181 | assert_in_delta(0.0, sin(-PI(N), N)) 182 | assert_in_delta(0.0, sin(PI(N) * 21, N)) 183 | assert_in_delta(0.0, sin(PI(N) * 30, N)) 184 | assert_in_delta(-1.0, sin(PI(N) * BigDecimal("301.5"), N)) 185 | assert_in_exact_precision(BigDecimal('0.5'), sin(PI(100) / 6, 100), 100) 186 | assert_in_exact_precision(SQRT3 / 2, sin(PI(100) / 3, 100), 100) 187 | assert_in_exact_precision(SQRT2 / 2, sin(PI(100) / 4, 100), 100) 188 | assert_converge_in_precision {|n| sin(BigDecimal("1"), n) } 189 | assert_converge_in_precision {|n| sin(BigDecimal("1e50"), n) } 190 | assert_converge_in_precision {|n| sin(BigDecimal("1e-30"), n) } 191 | assert_converge_in_precision {|n| sin(BigDecimal(PI(50)), n) } 192 | assert_converge_in_precision {|n| sin(BigDecimal(PI(50) * 100), n) } 193 | assert_operator(sin(PI(30) / 2, 30), :<=, 1) 194 | assert_operator(sin(-PI(30) / 2, 30), :>=, -1) 195 | end 196 | 197 | def test_cos 198 | assert_in_delta(1.0, cos(BigDecimal("0.0"), N)) 199 | assert_in_delta(Math.sqrt(2.0) / 2, cos(PI(N) / 4, N)) 200 | assert_in_delta(0.0, cos(PI(N) / 2, N)) 201 | assert_in_delta(1.0, cos(PI(N) * 2, N)) 202 | assert_in_delta(-1.0, cos(PI(N), N)) 203 | assert_in_delta(0.0, cos(PI(N) / -2, N)) 204 | assert_in_delta(1.0, cos(PI(N) * -2, N)) 205 | assert_in_delta(-1.0, cos(-PI(N), N)) 206 | assert_in_delta(-1.0, cos(PI(N) * 21, N)) 207 | assert_in_delta(1.0, cos(PI(N) * 30, N)) 208 | assert_in_delta(0.0, cos(PI(N) * BigDecimal("301.5"), N)) 209 | assert_in_exact_precision(BigDecimal('0.5'), cos(PI(100) / 3, 100), 100) 210 | assert_in_exact_precision(SQRT3 / 2, cos(PI(100) / 6, 100), 100) 211 | assert_in_exact_precision(SQRT2 / 2, cos(PI(100) / 4, 100), 100) 212 | assert_converge_in_precision {|n| cos(BigDecimal("1"), n) } 213 | assert_converge_in_precision {|n| cos(BigDecimal("1e50"), n) } 214 | assert_converge_in_precision {|n| cos(BigDecimal(PI(50) / 2), n) } 215 | assert_converge_in_precision {|n| cos(BigDecimal(PI(50) * 201 / 2), n) } 216 | assert_operator(cos(PI(30), 30), :>=, -1) 217 | assert_operator(cos(PI(30) * 2, 30), :<=, 1) 218 | end 219 | 220 | def test_tan 221 | assert_in_delta(0.0, tan(BigDecimal("0.0"), N)) 222 | assert_in_delta(0.0, tan(PI(N), N)) 223 | assert_in_delta(1.0, tan(PI(N) / 4, N)) 224 | assert_in_delta(sqrt(BigDecimal(3), N), tan(PI(N) / 3, N)) 225 | assert_in_delta(sqrt(BigDecimal(3), 10 * N), tan(PI(10 * N) / 3, 10 * N)) 226 | assert_in_delta(0.0, tan(-PI(N), N)) 227 | assert_in_delta(-1.0, tan(-PI(N) / 4, N)) 228 | assert_in_delta(-sqrt(BigDecimal(3), N), tan(-PI(N) / 3, N)) 229 | assert_in_exact_precision(SQRT3, tan(PI(100) / 3, 100), 100) 230 | assert_converge_in_precision {|n| tan(1, n) } 231 | assert_converge_in_precision {|n| tan(BigMath::PI(50) / 2, n) } 232 | assert_converge_in_precision {|n| tan(BigMath::PI(50), n) } 233 | end 234 | 235 | def test_asin 236 | ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| 237 | assert_in_delta(Math.asin(x.to_f), asin(BigDecimal(x), N)) 238 | end 239 | assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("1.1"), N) } 240 | assert_raise(Math::DomainError) { BigMath.asin(BigDecimal("-1.1"), N) } 241 | assert_in_exact_precision(PI(100) / 6, asin(BigDecimal("0.5"), 100), 100) 242 | assert_converge_in_precision {|n| asin(BigDecimal("-0.4"), n) } 243 | assert_converge_in_precision {|n| asin(BigDecimal("0.3"), n) } 244 | assert_converge_in_precision {|n| asin(SQRT2 / 2, n) } 245 | assert_converge_in_precision {|n| asin(BigDecimal("0.9"), n) } 246 | assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 50}"), n) } 247 | assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 100}"), n) } 248 | assert_converge_in_precision {|n| asin(BigDecimal("0.#{"9" * 195}"), n) } 249 | assert_converge_in_precision {|n| asin(BigDecimal("1e-30"), n) } 250 | end 251 | 252 | def test_acos 253 | ["-1", "-0.9", "-0.1", "0", "0.1", "0.9", "1"].each do |x| 254 | assert_in_delta(Math.acos(x.to_f), acos(BigDecimal(x), N)) 255 | end 256 | assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("1.1"), N) } 257 | assert_raise(Math::DomainError) { BigMath.acos(BigDecimal("-1.1"), N) } 258 | assert_equal(0, acos(BigDecimal("1.0"), N)) 259 | assert_in_exact_precision(PI(100) / 3, acos(BigDecimal("0.5"), 100), 100) 260 | assert_converge_in_precision {|n| acos(BigDecimal("-0.4"), n) } 261 | assert_converge_in_precision {|n| acos(BigDecimal("0.3"), n) } 262 | assert_converge_in_precision {|n| acos(SQRT2 / 2, n) } 263 | assert_converge_in_precision {|n| acos(BigDecimal("0.9"), n) } 264 | assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 50}"), n) } 265 | assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 100}"), n) } 266 | assert_converge_in_precision {|n| acos(BigDecimal("0.#{"9" * 195}"), n) } 267 | assert_converge_in_precision {|n| acos(BigDecimal("1e-30"), n) } 268 | end 269 | 270 | def test_atan 271 | assert_equal(0.0, atan(BigDecimal("0.0"), N)) 272 | assert_in_delta(Math::PI/4, atan(BigDecimal("1.0"), N)) 273 | assert_in_delta(Math::PI/6, atan(sqrt(BigDecimal("3.0"), N) / 3, N)) 274 | assert_in_delta(Math::PI/2, atan(PINF, N)) 275 | assert_in_exact_precision(PI(100) / 3, atan(SQRT3, 100), 100) 276 | assert_equal(BigDecimal("0.823840753418636291769355073102514088959345624027952954058347023122539489"), 277 | atan(BigDecimal("1.08"), 72).round(72), '[ruby-dev:41257]') 278 | assert_converge_in_precision {|n| atan(BigDecimal("2"), n)} 279 | assert_converge_in_precision {|n| atan(BigDecimal("1e-30"), n)} 280 | assert_converge_in_precision {|n| atan(BigDecimal("1e30"), n)} 281 | end 282 | 283 | def test_atan2 284 | zero = BigDecimal(0) 285 | one = BigDecimal(1) 286 | assert_equal(0, atan2(zero, zero, N)) 287 | assert_equal(0, atan2(zero, one, N)) 288 | [MINF, -one, -zero, zero, one, PINF].repeated_permutation(2) do |y, x| 289 | assert_in_delta(Math::atan2(y.to_f, x.to_f), atan2(y, x, N)) 290 | end 291 | assert_in_exact_precision(PI(100), atan2(zero, -one, 100), 100) 292 | assert_in_exact_precision(PI(100) / 2, atan2(one, zero, 100), 100) 293 | assert_in_exact_precision(-PI(100) / 2, atan2(-one, zero, 100), 100) 294 | assert_in_exact_precision(PI(100) / 3, atan2(BigDecimal(3), SQRT3, 100), 100) 295 | assert_in_exact_precision(PI(100) / 6, atan2(SQRT3, BigDecimal(3), 100), 100) 296 | assert_converge_in_precision {|n| atan2(SQRT2, SQRT3, n) } 297 | ['-1e20', '-2', '-1e-30', '1e-30', '2', '1e20'].repeated_permutation(2) do |y, x| 298 | assert_in_delta(Math.atan2(y.to_f, x.to_f), atan2(BigDecimal(y), BigDecimal(x), N)) 299 | assert_converge_in_precision {|n| atan2(BigDecimal(y), BigDecimal(x), n) } 300 | end 301 | end 302 | 303 | def test_hyperbolic 304 | [-1, 0, 0.5, 1, 10].each do |x| 305 | assert_in_delta(Math.sinh(x), sinh(BigDecimal(x.to_s), N)) 306 | assert_in_delta(Math.cosh(x), cosh(BigDecimal(x.to_s), N)) 307 | assert_in_delta(Math.tanh(x), tanh(BigDecimal(x.to_s), N)) 308 | end 309 | assert_negative_infinite_calculation { sinh(MINF, N) } 310 | assert_positive_infinite_calculation { sinh(PINF, N) } 311 | assert_positive_infinite_calculation { cosh(MINF, N) } 312 | assert_positive_infinite_calculation { cosh(PINF, N) } 313 | assert_equal(-1, tanh(MINF, N)) 314 | assert_equal(+1, tanh(PINF, N)) 315 | 316 | x = BigDecimal("0.3") 317 | assert_in_exact_precision(sinh(x, 120) / cosh(x, 120), tanh(x, 100), 100) 318 | assert_in_exact_precision(tanh(x, 120) * cosh(x, 120), sinh(x, 100), 100) 319 | assert_in_exact_precision(sinh(x, 120) / tanh(x, 120), cosh(x, 100), 100) 320 | 321 | e = E(120) 322 | assert_in_exact_precision((e - 1 / e) / 2, sinh(BigDecimal(1), 100), 100) 323 | assert_in_exact_precision((e + 1 / e) / 2, cosh(BigDecimal(1), 100), 100) 324 | assert_in_exact_precision((e - 1 / e) / (e + 1 / e), tanh(BigDecimal(1), 100), 100) 325 | 326 | ["1e-30", "0.2", SQRT2, "10", "100"].each do |x| 327 | assert_converge_in_precision {|n| sinh(BigDecimal(x), n)} 328 | assert_converge_in_precision {|n| cosh(BigDecimal(x), n)} 329 | assert_converge_in_precision {|n| tanh(BigDecimal(x), n)} 330 | end 331 | end 332 | 333 | def test_asinh 334 | [-3, 0.5, 10].each do |x| 335 | assert_in_delta(Math.asinh(x), asinh(BigDecimal(x.to_s), N)) 336 | end 337 | assert_equal(0, asinh(BigDecimal(0), N)) 338 | assert_positive_infinite_calculation { asinh(PINF, N) } 339 | assert_negative_infinite_calculation { asinh(MINF, N) } 340 | 341 | x = SQRT2 / 2 342 | assert_in_exact_precision(x, asinh(sinh(x, 120), 100), 100) 343 | 344 | ["1e-30", "0.2", "10", "100"].each do |x| 345 | assert_converge_in_precision {|n| asinh(BigDecimal(x), n)} 346 | end 347 | end 348 | 349 | def test_acosh 350 | [1.5, 2, 10].each do |x| 351 | assert_in_delta(Math.acosh(x), acosh(BigDecimal(x.to_s), N)) 352 | end 353 | assert_equal(0, acosh(BigDecimal(1), N)) 354 | assert_positive_infinite_calculation { acosh(PINF, N) } 355 | 356 | x = SQRT2 357 | assert_in_exact_precision(x, acosh(cosh(x, 120), 100), 100) 358 | 359 | ["1." + "0" * 30 + "1", "1.5", "2", "100"].each do |x| 360 | assert_converge_in_precision {|n| acosh(BigDecimal(x), n)} 361 | end 362 | end 363 | 364 | def test_atanh 365 | [-0.5, 0.1, 0.9].each do |x| 366 | assert_in_delta(Math.atanh(x), atanh(BigDecimal(x.to_s), N)) 367 | end 368 | assert_equal(0, atanh(BigDecimal(0), N)) 369 | assert_positive_infinite_calculation { atanh(BigDecimal(1), N) } 370 | assert_negative_infinite_calculation { atanh(BigDecimal(-1), N) } 371 | 372 | x = SQRT2 / 2 373 | assert_in_exact_precision(x, atanh(tanh(x, 120), 100), 100) 374 | 375 | ["1e-30", "0.5", "0.9" + "9" * 30].each do |x| 376 | assert_converge_in_precision {|n| atanh(BigDecimal(x), n)} 377 | end 378 | end 379 | 380 | def test_exp 381 | [-100, -2, 0.5, 10, 100].each do |x| 382 | assert_in_epsilon(Math.exp(x), exp(BigDecimal(x, 0), N)) 383 | end 384 | assert_equal(1, exp(BigDecimal("0"), N)) 385 | assert_in_exact_precision( 386 | BigDecimal("4.48168907033806482260205546011927581900574986836966705677265008278593667446671377298105383138245339138861635065183019577"), 387 | exp(BigDecimal("1.5"), 100), 388 | 100 389 | ) 390 | assert_converge_in_precision {|n| exp(BigDecimal("1"), n) } 391 | assert_converge_in_precision {|n| exp(BigDecimal("-2"), n) } 392 | assert_converge_in_precision {|n| exp(BigDecimal("-34"), n) } 393 | assert_converge_in_precision {|n| exp(BigDecimal("567"), n) } 394 | assert_converge_in_precision {|n| exp(SQRT2, n) } 395 | end 396 | 397 | def test_log 398 | assert_equal(0, log(BigDecimal("1.0"), 10)) 399 | assert_in_epsilon(Math.log(10)*1000, log(BigDecimal("1e1000"), 10)) 400 | assert_in_exact_precision( 401 | BigDecimal("2.3025850929940456840179914546843642076011014886287729760333279009675726096773524802359972050895982983419677840422862"), 402 | log(BigDecimal("10"), 100), 403 | 100 404 | ) 405 | assert_converge_in_precision {|n| log(BigDecimal("2"), n) } 406 | assert_converge_in_precision {|n| log(BigDecimal("1e-30") + 1, n) } 407 | assert_converge_in_precision {|n| log(BigDecimal("1e-30"), n) } 408 | assert_converge_in_precision {|n| log(BigDecimal("1e30"), n) } 409 | assert_converge_in_precision {|n| log(SQRT2, n) } 410 | assert_raise(Math::DomainError) {log(BigDecimal("-0.1"), 10)} 411 | begin 412 | x = BigDecimal("1E19999999999999") 413 | rescue FloatDomainError 414 | else 415 | unless x.infinite? 416 | assert_in_epsilon(Math.log(10) * 19999999999999, BigMath.log(x, 10)) 417 | end 418 | end 419 | end 420 | 421 | def test_log2 422 | assert_raise(Math::DomainError) { log2(BigDecimal("-0.01"), N) } 423 | assert_raise(Math::DomainError) { log2(MINF, N) } 424 | assert_positive_infinite_calculation { log2(PINF, N) } 425 | assert_in_exact_precision( 426 | BigDecimal("1.5849625007211561814537389439478165087598144076924810604557526545410982277943585625222804749180882420909806624750592"), 427 | log2(BigDecimal("3"), 100), 428 | 100 429 | ) 430 | assert_converge_in_precision {|n| log2(SQRT2, n) } 431 | assert_converge_in_precision {|n| log2(BigDecimal("3e20"), n) } 432 | assert_converge_in_precision {|n| log2(BigDecimal("1e-20") + 1, n) } 433 | [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| 434 | BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) 435 | [0, 1, 2, 11, 123].each do |n| 436 | assert_equal(n, log2(BigDecimal(2**n), N)) 437 | end 438 | end 439 | end 440 | 441 | def test_log10 442 | assert_raise(Math::DomainError) { log10(BigDecimal("-0.01"), N) } 443 | assert_raise(Math::DomainError) { log10(MINF, N) } 444 | assert_positive_infinite_calculation { log10(PINF, N) } 445 | assert_in_exact_precision( 446 | BigDecimal("0.4771212547196624372950279032551153092001288641906958648298656403052291527836611230429683556476163015104646927682520"), 447 | log10(BigDecimal("3"), 100), 448 | 100 449 | ) 450 | assert_converge_in_precision {|n| log10(SQRT2, n) } 451 | assert_converge_in_precision {|n| log10(BigDecimal("3e20"), n) } 452 | assert_converge_in_precision {|n| log10(BigDecimal("1e-20") + 1, n) } 453 | [BigDecimal::ROUND_UP, BigDecimal::ROUND_DOWN].each do |round_mode| 454 | BigDecimal.mode(BigDecimal::ROUND_MODE, round_mode) 455 | [0, 1, 2, 11, 123].each do |n| 456 | assert_equal(n, log10(BigDecimal(10**n), N)) 457 | end 458 | end 459 | end 460 | 461 | def test_log1p 462 | assert_raise(Math::DomainError) { log1p(MINF, N) } 463 | assert_raise(Math::DomainError) { log1p(BigDecimal("-1.01"), N) } 464 | assert_in_epsilon(Math.log(0.01), log1p(BigDecimal("-0.99"), N)) 465 | assert_positive_infinite_calculation { log1p(PINF, N) } 466 | assert_in_exact_precision(log(1 + BigDecimal("1e-20"), 100), log1p(BigDecimal("1e-20"), 100), 100) 467 | end 468 | 469 | def test_expm1 470 | assert_equal(-1, expm1(MINF, N)) 471 | assert_positive_infinite_calculation { expm1(PINF, N) } 472 | assert_equal(-1, expm1(BigDecimal("-400"), 100)) 473 | assert_equal(-1, expm1(BigDecimal("-231"), 100)) 474 | assert_not_equal(-1, expm1(BigDecimal("-229"), 100)) 475 | assert_in_exact_precision(exp(-220, 100) - 1, expm1(BigDecimal("-220"), 100), 100) 476 | assert_in_exact_precision(exp(-3, 100) - 1, expm1(BigDecimal("-3"), 100), 100) 477 | assert_in_exact_precision(exp(BigDecimal("1.23e-10"), 120) - 1, expm1(BigDecimal("1.23e-10"), 100), 100) 478 | assert_in_exact_precision(exp(123, 120) - 1, expm1(BigDecimal("123"), 100), 100) 479 | assert_equal(exp(BigDecimal("1e+12"), N), expm1(BigDecimal("1e+12"), N)) 480 | end 481 | 482 | def test_erf 483 | [-0.5, 0.1, 0.3, 2.1, 3.3].each do |x| 484 | assert_in_epsilon(Math.erf(x), BigMath.erf(BigDecimal(x.to_s), N)) 485 | end 486 | assert_equal(1, BigMath.erf(PINF, N)) 487 | assert_equal(-1, BigMath.erf(MINF, N)) 488 | assert_equal(1, BigMath.erf(BigDecimal(1000), 100)) 489 | assert_equal(-1, BigMath.erf(BigDecimal(-1000), 100)) 490 | assert_equal(1, BigMath.erf(BigDecimal(10000000), 100)) 491 | assert_not_equal(1, BigMath.erf(BigDecimal(10), 45)) 492 | assert_not_equal(1, BigMath.erf(BigDecimal(15), 100)) 493 | assert_equal(1, BigMath.erf(BigDecimal('1e400'), 10)) 494 | assert_equal(-1, BigMath.erf(BigDecimal('-1e400'), 10)) 495 | assert_equal(BigMath.erf(BigDecimal('1e-300'), N) * BigDecimal('1e-100'), BigMath.erf(BigDecimal('1e-400'), N)) 496 | assert_equal( 497 | BigDecimal("0.9953222650189527341620692563672529286108917970400600767383523262004372807199951773676290080196806805"), 498 | BigMath.erf(BigDecimal("2"), 100) 499 | ) 500 | assert_converge_in_precision {|n| BigMath.erf(BigDecimal("1e-30"), n) } 501 | assert_converge_in_precision {|n| BigMath.erf(BigDecimal("0.3"), n) } 502 | assert_converge_in_precision {|n| BigMath.erf(SQRT2, n) } 503 | end 504 | 505 | def test_erfc 506 | [-0.5, 0.1, 0.3, 2.1, 3.3].each do |x| 507 | assert_in_epsilon(Math.erfc(x), BigMath.erfc(BigDecimal(x.to_s), N)) 508 | end 509 | assert_equal(0, BigMath.erfc(PINF, N)) 510 | assert_equal(2, BigMath.erfc(MINF, N)) 511 | assert_equal(0, BigMath.erfc(BigDecimal('1e400'), 10)) 512 | assert_equal(2, BigMath.erfc(BigDecimal('-1e400'), 10)) 513 | assert_equal(1, BigMath.erfc(BigDecimal('1e-400'), N)) 514 | 515 | # erfc with taylor series 516 | assert_equal( 517 | BigDecimal("2.088487583762544757000786294957788611560818119321163727012213713938174695833440290610766384285723554e-45"), 518 | BigMath.erfc(BigDecimal("10"), 100) 519 | ) 520 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(0.3), n) } 521 | assert_converge_in_precision {|n| BigMath.erfc(SQRT2, n) } 522 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(8), n) } 523 | # erfc with asymptotic expansion 524 | assert_equal( 525 | BigDecimal("1.896961059966276509268278259713415434936907563929186183462834752900411805205111886605256690776760041e-697"), 526 | BigMath.erfc(BigDecimal("40"), 100) 527 | ) 528 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(30), n) } 529 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(-2), n) } 530 | assert_converge_in_precision {|n| BigMath.erfc(30 * SQRT2, n) } 531 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(50), n) } 532 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(60000), n) } 533 | # Near crossover point between taylor series and asymptotic expansion around prec=150 534 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(19.5), n) } 535 | assert_converge_in_precision {|n| BigMath.erfc(BigDecimal(20.5), n) } 536 | end 537 | 538 | def test_gamma 539 | [-1.8, -0.7, 0.6, 1.5, 2.4].each do |x| 540 | assert_in_epsilon(Math.gamma(x), gamma(BigDecimal(x.to_s), N)) 541 | end 542 | [1, 2, 3, 10, 16].each do |x| 543 | assert_equal(Math.gamma(x).round, gamma(BigDecimal(x), N)) 544 | end 545 | sqrt_pi = PI(120).sqrt(120) 546 | assert_equal(sqrt_pi.mult(1, 100), gamma(BigDecimal("0.5"), 100)) 547 | assert_equal((sqrt_pi * 4).div(3, 100), gamma(BigDecimal("-1.5"), 100)) 548 | assert_equal( 549 | BigDecimal('0.28242294079603478742934215780245355184774949260912e456569'), 550 | BigMath.gamma(100000, 50) 551 | ) 552 | assert_converge_in_precision {|n| gamma(BigDecimal("0.3"), n) } 553 | assert_converge_in_precision {|n| gamma(BigDecimal("-1.9" + "9" * 30), n) } 554 | assert_converge_in_precision {|n| gamma(BigDecimal("1234.56789"), n) } 555 | assert_converge_in_precision {|n| gamma(BigDecimal("-987.654321"), n) } 556 | end 557 | 558 | def test_lgamma 559 | [-2, -1, 0].each do |x| 560 | l, sign = lgamma(BigDecimal(x), N) 561 | assert(l.infinite?) 562 | assert_equal(1, sign) 563 | end 564 | [-1.8, -0.7, 0.6, 1, 1.5, 2, 2.4, 3, 1e+300].each do |x| 565 | l, sign = Math.lgamma(x) 566 | bigl, bigsign = lgamma(BigDecimal(x.to_s), N) 567 | assert_in_epsilon(l, bigl) 568 | assert_equal(sign, bigsign) 569 | end 570 | assert_equal([BigMath.log(PI(120).sqrt(120), 100), 1], lgamma(BigDecimal("0.5"), 100)) 571 | assert_converge_in_precision {|n| lgamma(BigDecimal("0." + "9" * 80), n).first } 572 | assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "0" * 80 + "1"), n).first } 573 | assert_converge_in_precision {|n| lgamma(BigDecimal("1." + "9" * 80), n).first } 574 | assert_converge_in_precision {|n| lgamma(BigDecimal("2." + "0" * 80 + "1"), n).first } 575 | assert_converge_in_precision {|n| lgamma(BigDecimal("-1." + "9" * 30), n).first } 576 | assert_converge_in_precision {|n| lgamma(BigDecimal("-3." + "0" * 30 + "1"), n).first } 577 | assert_converge_in_precision {|n| lgamma(BigDecimal("10"), n).first } 578 | assert_converge_in_precision {|n| lgamma(BigDecimal("0.3"), n).first } 579 | assert_converge_in_precision {|n| lgamma(BigDecimal("-1.9" + "9" * 30), n).first } 580 | assert_converge_in_precision {|n| lgamma(BigDecimal("987.65421"), n).first } 581 | assert_converge_in_precision {|n| lgamma(BigDecimal("-1234.56789"), n).first } 582 | assert_converge_in_precision {|n| lgamma(BigDecimal("1e+400"), n).first } 583 | 584 | # gamma close 1 or -1 cases 585 | assert_converge_in_precision {|n| lgamma(BigDecimal('-3.143580888349980058694358781820227899566'), n).first } 586 | assert_converge_in_precision {|n| lgamma(BigDecimal('-4.991544640560047722345260122806465721667'), n).first } 587 | end 588 | 589 | def test_frexp 590 | BigDecimal.save_limit do 591 | BigDecimal.limit(3) 592 | assert_equal([BigDecimal("-0.123456"), 10], BigMath.frexp(BigDecimal("-0.123456e10"))) 593 | assert_equal([BigDecimal("0.123456"), -10], BigMath.frexp(BigDecimal("0.123456e-10"))) 594 | assert_equal([BigDecimal("0.123456789"), 9], BigMath.frexp(123456789)) 595 | assert_equal([BigDecimal(0), 0], BigMath.frexp(BigDecimal(0))) 596 | assert_equal([BigDecimal::NAN, 0], BigMath.frexp(BigDecimal::NAN)) 597 | assert_equal([BigDecimal::INFINITY, 0], BigMath.frexp(BigDecimal::INFINITY)) 598 | end 599 | end 600 | 601 | def test_ldexp 602 | BigDecimal.save_limit do 603 | BigDecimal.limit(3) 604 | assert_equal(BigDecimal("-0.123456e10"), BigMath.ldexp(BigDecimal("-0.123456"), 10)) 605 | assert_equal(BigDecimal("0.123456e20"), BigMath.ldexp(BigDecimal("0.123456e10"), 10.9)) 606 | assert_equal(BigDecimal("0.123456e-10"), BigMath.ldexp(BigDecimal("0.123456"), -10)) 607 | assert_equal(BigDecimal("0.123456789e19"), BigMath.ldexp(123456789, 10)) 608 | assert(BigMath.ldexp(BigDecimal::NAN, 10).nan?) 609 | assert_equal(BigDecimal::INFINITY, BigMath.ldexp(BigDecimal::INFINITY, 10)) 610 | end 611 | end 612 | end 613 | -------------------------------------------------------------------------------- /lib/bigdecimal/math.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'bigdecimal' 3 | 4 | # 5 | #-- 6 | # Contents: 7 | # sqrt(x, prec) 8 | # cbrt(x, prec) 9 | # hypot(x, y, prec) 10 | # sin (x, prec) 11 | # cos (x, prec) 12 | # tan (x, prec) 13 | # asin(x, prec) 14 | # acos(x, prec) 15 | # atan(x, prec) 16 | # atan2(y, x, prec) 17 | # sinh (x, prec) 18 | # cosh (x, prec) 19 | # tanh (x, prec) 20 | # asinh(x, prec) 21 | # acosh(x, prec) 22 | # atanh(x, prec) 23 | # log2 (x, prec) 24 | # log10(x, prec) 25 | # log1p(x, prec) 26 | # expm1(x, prec) 27 | # erf (x, prec) 28 | # erfc(x, prec) 29 | # gamma(x, prec) 30 | # lgamma(x, prec) 31 | # frexp(x) 32 | # ldexp(x, exponent) 33 | # PI (prec) 34 | # E (prec) == exp(1.0,prec) 35 | # 36 | # where: 37 | # x, y ... BigDecimal number to be computed. 38 | # prec ... Number of digits to be obtained. 39 | #++ 40 | # 41 | # Provides mathematical functions. 42 | # 43 | # Example: 44 | # 45 | # require "bigdecimal/math" 46 | # 47 | # include BigMath 48 | # 49 | # a = BigDecimal((PI(49)/2).to_s) 50 | # puts sin(a,100) # => 0.9999999999...9999999986e0 51 | # 52 | module BigMath 53 | module_function 54 | 55 | # call-seq: 56 | # sqrt(decimal, numeric) -> BigDecimal 57 | # 58 | # Computes the square root of +decimal+ to the specified number of digits of 59 | # precision, +numeric+. 60 | # 61 | # BigMath.sqrt(BigDecimal('2'), 32).to_s 62 | # #=> "0.14142135623730950488016887242097e1" 63 | # 64 | def sqrt(x, prec) 65 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :sqrt) 66 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sqrt) 67 | x.sqrt(prec) 68 | end 69 | 70 | 71 | # Returns [sign, reduced_x] where reduced_x is in -pi/2..pi/2 72 | # and satisfies sin(x) = sign * sin(reduced_x) 73 | # If add_half_pi is true, adds pi/2 to x before reduction. 74 | # Precision of pi is adjusted to ensure reduced_x has the required precision. 75 | private_class_method def _sin_periodic_reduction(x, prec, add_half_pi: false) # :nodoc: 76 | return [1, x] if -Math::PI/2 <= x && x <= Math::PI/2 && !add_half_pi 77 | 78 | mod_prec = prec + BigDecimal.double_fig 79 | pi_extra_prec = [x.exponent, 0].max + BigDecimal.double_fig 80 | while true 81 | pi = PI(mod_prec + pi_extra_prec) 82 | half_pi = pi / 2 83 | div, mod = (add_half_pi ? x + pi : x + half_pi).divmod(pi) 84 | mod -= half_pi 85 | if mod.zero? || mod_prec + mod.exponent <= 0 86 | # mod is too small to estimate required pi precision 87 | mod_prec = mod_prec * 3 / 2 + BigDecimal.double_fig 88 | elsif mod_prec + mod.exponent < prec 89 | # Estimate required precision of pi 90 | mod_prec = prec - mod.exponent + BigDecimal.double_fig 91 | else 92 | return [div % 2 == 0 ? 1 : -1, mod.mult(1, prec)] 93 | end 94 | end 95 | end 96 | 97 | # call-seq: 98 | # cbrt(decimal, numeric) -> BigDecimal 99 | # 100 | # Computes the cube root of +decimal+ to the specified number of digits of 101 | # precision, +numeric+. 102 | # 103 | # BigMath.cbrt(BigDecimal('2'), 32).to_s 104 | # #=> "0.12599210498948731647672106072782e1" 105 | # 106 | def cbrt(x, prec) 107 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :cbrt) 108 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cbrt) 109 | return BigDecimal::Internal.nan_computation_result if x.nan? 110 | return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? 111 | return BigDecimal(0) if x.zero? 112 | 113 | x = -x if neg = x < 0 114 | ex = x.exponent / 3 115 | x = x._decimal_shift(-3 * ex) 116 | y = BigDecimal(Math.cbrt(x.to_f), 0) 117 | precs = [prec + BigDecimal.double_fig] 118 | precs << 2 + precs.last / 2 while precs.last > BigDecimal.double_fig 119 | precs.reverse_each do |p| 120 | y = (2 * y + x.div(y, p).div(y, p)).div(3, p) 121 | end 122 | y._decimal_shift(ex).mult(neg ? -1 : 1, prec) 123 | end 124 | 125 | # call-seq: 126 | # hypot(x, y, numeric) -> BigDecimal 127 | # 128 | # Returns sqrt(x**2 + y**2) to the specified number of digits of 129 | # precision, +numeric+. 130 | # 131 | # BigMath.hypot(BigDecimal('1'), BigDecimal('2'), 32).to_s 132 | # #=> "0.22360679774997896964091736687313e1" 133 | # 134 | def hypot(x, y, prec) 135 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :hypot) 136 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :hypot) 137 | y = BigDecimal::Internal.coerce_to_bigdecimal(y, prec, :hypot) 138 | return BigDecimal::Internal.nan_computation_result if x.nan? || y.nan? 139 | return BigDecimal::Internal.infinity_computation_result if x.infinite? || y.infinite? 140 | prec2 = prec + BigDecimal.double_fig 141 | sqrt(x.mult(x, prec2) + y.mult(y, prec2), prec) 142 | end 143 | 144 | # call-seq: 145 | # sin(decimal, numeric) -> BigDecimal 146 | # 147 | # Computes the sine of +decimal+ to the specified number of digits of 148 | # precision, +numeric+. 149 | # 150 | # If +decimal+ is Infinity or NaN, returns NaN. 151 | # 152 | # BigMath.sin(BigMath.PI(5)/4, 32).to_s 153 | # #=> "0.70710807985947359435812921837984e0" 154 | # 155 | def sin(x, prec) 156 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :sin) 157 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sin) 158 | return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? 159 | n = prec + BigDecimal.double_fig 160 | one = BigDecimal("1") 161 | two = BigDecimal("2") 162 | sign, x = _sin_periodic_reduction(x, n) 163 | x1 = x 164 | x2 = x.mult(x,n) 165 | y = x 166 | d = y 167 | i = one 168 | z = one 169 | while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) 170 | m = BigDecimal.double_fig if m < BigDecimal.double_fig 171 | x1 = -x2.mult(x1,n) 172 | i += two 173 | z *= (i-one) * i 174 | d = x1.div(z,m) 175 | y += d 176 | end 177 | y = BigDecimal("1") if y > 1 178 | y.mult(sign, prec) 179 | end 180 | 181 | # call-seq: 182 | # cos(decimal, numeric) -> BigDecimal 183 | # 184 | # Computes the cosine of +decimal+ to the specified number of digits of 185 | # precision, +numeric+. 186 | # 187 | # If +decimal+ is Infinity or NaN, returns NaN. 188 | # 189 | # BigMath.cos(BigMath.PI(16), 32).to_s 190 | # #=> "-0.99999999999999999999999999999997e0" 191 | # 192 | def cos(x, prec) 193 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :cos) 194 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cos) 195 | return BigDecimal::Internal.nan_computation_result if x.infinite? || x.nan? 196 | sign, x = _sin_periodic_reduction(x, prec + BigDecimal.double_fig, add_half_pi: true) 197 | sign * sin(x, prec) 198 | end 199 | 200 | # call-seq: 201 | # tan(decimal, numeric) -> BigDecimal 202 | # 203 | # Computes the tangent of +decimal+ to the specified number of digits of 204 | # precision, +numeric+. 205 | # 206 | # If +decimal+ is Infinity or NaN, returns NaN. 207 | # 208 | # BigMath.tan(BigDecimal("0.0"), 4).to_s 209 | # #=> "0.0" 210 | # 211 | # BigMath.tan(BigMath.PI(24) / 4, 32).to_s 212 | # #=> "0.99999999999999999999999830836025e0" 213 | # 214 | def tan(x, prec) 215 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :tan) 216 | sin(x, prec + BigDecimal.double_fig).div(cos(x, prec + BigDecimal.double_fig), prec) 217 | end 218 | 219 | # call-seq: 220 | # asin(decimal, numeric) -> BigDecimal 221 | # 222 | # Computes the arcsine of +decimal+ to the specified number of digits of 223 | # precision, +numeric+. 224 | # 225 | # If +decimal+ is NaN, returns NaN. 226 | # 227 | # BigMath.asin(BigDecimal('0.5'), 32).to_s 228 | # #=> "0.52359877559829887307710723054658e0" 229 | # 230 | def asin(x, prec) 231 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :asin) 232 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :asin) 233 | raise Math::DomainError, "Out of domain argument for asin" if x < -1 || x > 1 234 | return BigDecimal::Internal.nan_computation_result if x.nan? 235 | 236 | prec2 = prec + BigDecimal.double_fig 237 | cos = (1 - x**2).sqrt(prec2) 238 | if cos.zero? 239 | PI(prec2).div(x > 0 ? 2 : -2, prec) 240 | else 241 | atan(x.div(cos, prec2), prec) 242 | end 243 | end 244 | 245 | # call-seq: 246 | # acos(decimal, numeric) -> BigDecimal 247 | # 248 | # Computes the arccosine of +decimal+ to the specified number of digits of 249 | # precision, +numeric+. 250 | # 251 | # If +decimal+ is NaN, returns NaN. 252 | # 253 | # BigMath.acos(BigDecimal('0.5'), 32).to_s 254 | # #=> "0.10471975511965977461542144610932e1" 255 | # 256 | def acos(x, prec) 257 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :acos) 258 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :acos) 259 | raise Math::DomainError, "Out of domain argument for acos" if x < -1 || x > 1 260 | return BigDecimal::Internal.nan_computation_result if x.nan? 261 | 262 | prec2 = prec + BigDecimal.double_fig 263 | return (PI(prec2) / 2).sub(asin(x, prec2), prec) if x < 0 264 | return PI(prec2).div(2, prec) if x.zero? 265 | 266 | sin = (1 - x**2).sqrt(prec2) 267 | atan(sin.div(x, prec2), prec) 268 | end 269 | 270 | # call-seq: 271 | # atan(decimal, numeric) -> BigDecimal 272 | # 273 | # Computes the arctangent of +decimal+ to the specified number of digits of 274 | # precision, +numeric+. 275 | # 276 | # If +decimal+ is NaN, returns NaN. 277 | # 278 | # BigMath.atan(BigDecimal('-1'), 32).to_s 279 | # #=> "-0.78539816339744830961566084581988e0" 280 | # 281 | def atan(x, prec) 282 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan) 283 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan) 284 | return BigDecimal::Internal.nan_computation_result if x.nan? 285 | n = prec + BigDecimal.double_fig 286 | pi = PI(n) 287 | x = -x if neg = x < 0 288 | return pi.div(neg ? -2 : 2, prec) if x.infinite? 289 | return pi.div(neg ? -4 : 4, prec) if x.round(prec) == 1 290 | x = BigDecimal("1").div(x, n) if inv = x > 1 291 | x = (-1 + sqrt(1 + x.mult(x, n), n)).div(x, n) if dbl = x > 0.5 292 | y = x 293 | d = y 294 | t = x 295 | r = BigDecimal("3") 296 | x2 = x.mult(x,n) 297 | while d.nonzero? && ((m = n - (y.exponent - d.exponent).abs) > 0) 298 | m = BigDecimal.double_fig if m < BigDecimal.double_fig 299 | t = -t.mult(x2,n) 300 | d = t.div(r,m) 301 | y += d 302 | r += 2 303 | end 304 | y *= 2 if dbl 305 | y = pi / 2 - y if inv 306 | y = -y if neg 307 | y.mult(1, prec) 308 | end 309 | 310 | # call-seq: 311 | # atan2(decimal, decimal, numeric) -> BigDecimal 312 | # 313 | # Computes the arctangent of y and x to the specified number of digits of 314 | # precision, +numeric+. 315 | # 316 | # BigMath.atan2(BigDecimal('-1'), BigDecimal('1'), 32).to_s 317 | # #=> "-0.78539816339744830961566084581988e0" 318 | # 319 | def atan2(y, x, prec) 320 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :atan2) 321 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atan2) 322 | y = BigDecimal::Internal.coerce_to_bigdecimal(y, prec, :atan2) 323 | return BigDecimal::Internal.nan_computation_result if x.nan? || y.nan? 324 | 325 | if x.infinite? || y.infinite? 326 | one = BigDecimal(1) 327 | zero = BigDecimal(0) 328 | x = x.infinite? ? (x > 0 ? one : -one) : zero 329 | y = y.infinite? ? (y > 0 ? one : -one) : y.sign * zero 330 | end 331 | 332 | return x.sign >= 0 ? BigDecimal(0) : y.sign * PI(prec) if y.zero? 333 | 334 | y = -y if neg = y < 0 335 | xlarge = y.abs < x.abs 336 | prec2 = prec + BigDecimal.double_fig 337 | if x > 0 338 | v = xlarge ? atan(y.div(x, prec2), prec) : PI(prec2) / 2 - atan(x.div(y, prec2), prec2) 339 | else 340 | v = xlarge ? PI(prec2) - atan(-y.div(x, prec2), prec2) : PI(prec2) / 2 + atan(x.div(-y, prec2), prec2) 341 | end 342 | v.mult(neg ? -1 : 1, prec) 343 | end 344 | 345 | # call-seq: 346 | # sinh(decimal, numeric) -> BigDecimal 347 | # 348 | # Computes the hyperbolic sine of +decimal+ to the specified number of digits of 349 | # precision, +numeric+. 350 | # 351 | # If +decimal+ is NaN, returns NaN. 352 | # 353 | # BigMath.sinh(BigDecimal('1'), 32).to_s 354 | # #=> "0.11752011936438014568823818505956e1" 355 | # 356 | def sinh(x, prec) 357 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :sinh) 358 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :sinh) 359 | return BigDecimal::Internal.nan_computation_result if x.nan? 360 | return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? 361 | 362 | prec2 = prec + BigDecimal.double_fig 363 | prec2 -= x.exponent if x.exponent < 0 364 | e = exp(x, prec2) 365 | (e - BigDecimal(1).div(e, prec2)).div(2, prec) 366 | end 367 | 368 | # call-seq: 369 | # cosh(decimal, numeric) -> BigDecimal 370 | # 371 | # Computes the hyperbolic cosine of +decimal+ to the specified number of digits of 372 | # precision, +numeric+. 373 | # 374 | # If +decimal+ is NaN, returns NaN. 375 | # 376 | # BigMath.cosh(BigDecimal('1'), 32).to_s 377 | # #=> "0.15430806348152437784779056207571e1" 378 | # 379 | def cosh(x, prec) 380 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :cosh) 381 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :cosh) 382 | return BigDecimal::Internal.nan_computation_result if x.nan? 383 | return BigDecimal::Internal.infinity_computation_result if x.infinite? 384 | 385 | prec2 = prec + BigDecimal.double_fig 386 | e = exp(x, prec2) 387 | (e + BigDecimal(1).div(e, prec2)).div(2, prec) 388 | end 389 | 390 | # call-seq: 391 | # tanh(decimal, numeric) -> BigDecimal 392 | # 393 | # Computes the hyperbolic tangent of +decimal+ to the specified number of digits of 394 | # precision, +numeric+. 395 | # 396 | # If +decimal+ is NaN, returns NaN. 397 | # 398 | # BigMath.tanh(BigDecimal('1'), 32).to_s 399 | # #=> "0.76159415595576488811945828260479e0" 400 | # 401 | def tanh(x, prec) 402 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :tanh) 403 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :tanh) 404 | return BigDecimal::Internal.nan_computation_result if x.nan? 405 | return BigDecimal(x.infinite?) if x.infinite? 406 | 407 | prec2 = prec + BigDecimal.double_fig + [-x.exponent, 0].max 408 | e = exp(x, prec2) 409 | einv = BigDecimal(1).div(e, prec2) 410 | (e - einv).div(e + einv, prec) 411 | end 412 | 413 | # call-seq: 414 | # asinh(decimal, numeric) -> BigDecimal 415 | # 416 | # Computes the inverse hyperbolic sine of +decimal+ to the specified number of digits of 417 | # precision, +numeric+. 418 | # 419 | # If +decimal+ is NaN, returns NaN. 420 | # 421 | # BigMath.asinh(BigDecimal('1'), 32).to_s 422 | # #=> "0.88137358701954302523260932497979e0" 423 | # 424 | def asinh(x, prec) 425 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :asinh) 426 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :asinh) 427 | return BigDecimal::Internal.nan_computation_result if x.nan? 428 | return BigDecimal::Internal.infinity_computation_result * x.infinite? if x.infinite? 429 | return -asinh(-x, prec) if x < 0 430 | 431 | sqrt_prec = prec + [-x.exponent, 0].max + BigDecimal.double_fig 432 | log(x + sqrt(x**2 + 1, sqrt_prec), prec) 433 | end 434 | 435 | # call-seq: 436 | # acosh(decimal, numeric) -> BigDecimal 437 | # 438 | # Computes the inverse hyperbolic cosine of +decimal+ to the specified number of digits of 439 | # precision, +numeric+. 440 | # 441 | # If +decimal+ is NaN, returns NaN. 442 | # 443 | # BigMath.acosh(BigDecimal('2'), 32).to_s 444 | # #=> "0.1316957896924816708625046347308e1" 445 | # 446 | def acosh(x, prec) 447 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :acosh) 448 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :acosh) 449 | raise Math::DomainError, "Out of domain argument for acosh" if x < 1 450 | return BigDecimal::Internal.infinity_computation_result if x.infinite? 451 | return BigDecimal::Internal.nan_computation_result if x.nan? 452 | 453 | log(x + sqrt(x**2 - 1, prec + BigDecimal.double_fig), prec) 454 | end 455 | 456 | # call-seq: 457 | # atanh(decimal, numeric) -> BigDecimal 458 | # 459 | # Computes the inverse hyperbolic tangent of +decimal+ to the specified number of digits of 460 | # precision, +numeric+. 461 | # 462 | # If +decimal+ is NaN, returns NaN. 463 | # 464 | # BigMath.atanh(BigDecimal('0.5'), 32).to_s 465 | # #=> "0.54930614433405484569762261846126e0" 466 | # 467 | def atanh(x, prec) 468 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :atanh) 469 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :atanh) 470 | raise Math::DomainError, "Out of domain argument for atanh" if x < -1 || x > 1 471 | return BigDecimal::Internal.nan_computation_result if x.nan? 472 | return BigDecimal::Internal.infinity_computation_result if x == 1 473 | return -BigDecimal::Internal.infinity_computation_result if x == -1 474 | 475 | prec2 = prec + BigDecimal.double_fig 476 | (log(x + 1, prec2) - log(1 - x, prec2)).div(2, prec) 477 | end 478 | 479 | # call-seq: 480 | # BigMath.log2(decimal, numeric) -> BigDecimal 481 | # 482 | # Computes the base 2 logarithm of +decimal+ to the specified number of 483 | # digits of precision, +numeric+. 484 | # 485 | # If +decimal+ is zero or negative, raises Math::DomainError. 486 | # 487 | # If +decimal+ is positive infinity, returns Infinity. 488 | # 489 | # If +decimal+ is NaN, returns NaN. 490 | # 491 | # BigMath.log2(BigDecimal('3'), 32).to_s 492 | # #=> "0.15849625007211561814537389439478e1" 493 | # 494 | def log2(x, prec) 495 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :log2) 496 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log2) 497 | return BigDecimal::Internal.nan_computation_result if x.nan? 498 | return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 499 | 500 | prec2 = prec + BigDecimal.double_fig * 3 / 2 501 | v = log(x, prec2).div(log(BigDecimal(2), prec2), prec2) 502 | # Perform half-up rounding to calculate log2(2**n)==n correctly in every rounding mode 503 | v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) 504 | v.mult(1, prec) 505 | end 506 | 507 | # call-seq: 508 | # BigMath.log10(decimal, numeric) -> BigDecimal 509 | # 510 | # Computes the base 10 logarithm of +decimal+ to the specified number of 511 | # digits of precision, +numeric+. 512 | # 513 | # If +decimal+ is zero or negative, raises Math::DomainError. 514 | # 515 | # If +decimal+ is positive infinity, returns Infinity. 516 | # 517 | # If +decimal+ is NaN, returns NaN. 518 | # 519 | # BigMath.log10(BigDecimal('3'), 32).to_s 520 | # #=> "0.47712125471966243729502790325512e0" 521 | # 522 | def log10(x, prec) 523 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :log10) 524 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log10) 525 | return BigDecimal::Internal.nan_computation_result if x.nan? 526 | return BigDecimal::Internal.infinity_computation_result if x.infinite? == 1 527 | 528 | prec2 = prec + BigDecimal.double_fig * 3 / 2 529 | v = log(x, prec2).div(log(BigDecimal(10), prec2), prec2) 530 | # Perform half-up rounding to calculate log10(10**n)==n correctly in every rounding mode 531 | v = v.round(prec + BigDecimal.double_fig - (v.exponent < 0 ? v.exponent : 0), BigDecimal::ROUND_HALF_UP) 532 | v.mult(1, prec) 533 | end 534 | 535 | # call-seq: 536 | # BigMath.log1p(decimal, numeric) -> BigDecimal 537 | # 538 | # Computes log(1 + decimal) to the specified number of digits of precision, +numeric+. 539 | # 540 | # BigMath.log1p(BigDecimal('0.1'), 32).to_s 541 | # #=> "0.95310179804324860043952123280765e-1" 542 | # 543 | def log1p(x, prec) 544 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :log1p) 545 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :log1p) 546 | raise Math::DomainError, 'Out of domain argument for log1p' if x < -1 547 | 548 | return log(x + 1, prec) 549 | end 550 | 551 | # call-seq: 552 | # BigMath.expm1(decimal, numeric) -> BigDecimal 553 | # 554 | # Computes exp(decimal) - 1 to the specified number of digits of precision, +numeric+. 555 | # 556 | # BigMath.expm1(BigDecimal('0.1'), 32).to_s 557 | # #=> "0.10517091807564762481170782649025e0" 558 | # 559 | def expm1(x, prec) 560 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :expm1) 561 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :expm1) 562 | return BigDecimal(-1) if x.infinite? == -1 563 | 564 | exp_prec = prec 565 | if x < -1 566 | # log10(exp(x)) = x * log10(e) 567 | lg_e = 0.4342944819032518 568 | exp_prec = prec + (lg_e * x).ceil + BigDecimal.double_fig 569 | elsif x < 1 570 | exp_prec = prec - x.exponent + BigDecimal.double_fig 571 | else 572 | exp_prec = prec 573 | end 574 | 575 | return BigDecimal(-1) if exp_prec <= 0 576 | 577 | exp = exp(x, exp_prec) 578 | 579 | if exp.exponent > prec + BigDecimal.double_fig 580 | # Workaroudn for https://github.com/ruby/bigdecimal/issues/464 581 | exp 582 | else 583 | exp.sub(1, prec) 584 | end 585 | end 586 | 587 | # erf(decimal, numeric) -> BigDecimal 588 | # 589 | # Computes the error function of +decimal+ to the specified number of digits of 590 | # precision, +numeric+. 591 | # 592 | # If +decimal+ is NaN, returns NaN. 593 | # 594 | # BigMath.erf(BigDecimal('1'), 32).to_s 595 | # #=> "0.84270079294971486934122063508261e0" 596 | # 597 | def erf(x, prec) 598 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :erf) 599 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erf) 600 | return BigDecimal::Internal.nan_computation_result if x.nan? 601 | return BigDecimal(x.infinite?) if x.infinite? 602 | return BigDecimal(0) if x == 0 603 | return -erf(-x, prec) if x < 0 604 | return BigDecimal(1) if x > 5000000000 # erf(5000000000) > 1 - 1e-10000000000000000000 605 | 606 | if x > 8 607 | xf = x.to_f 608 | log10_erfc = -xf ** 2 / Math.log(10) - Math.log10(xf * Math::PI ** 0.5) 609 | erfc_prec = [prec + log10_erfc.ceil, 1].max 610 | erfc = _erfc_asymptotic(x, erfc_prec) 611 | if erfc 612 | # Workaround for https://github.com/ruby/bigdecimal/issues/464 613 | return BigDecimal(1) if erfc.exponent < -prec - BigDecimal.double_fig 614 | 615 | return BigDecimal(1).sub(erfc, prec) 616 | end 617 | end 618 | 619 | prec2 = prec + BigDecimal.double_fig 620 | x_smallprec = x.mult(1, Integer.sqrt(prec2) / 2) 621 | # Taylor series of x with small precision is fast 622 | erf1 = _erf_taylor(x_smallprec, BigDecimal(0), BigDecimal(0), prec2) 623 | # Taylor series converges quickly for small x 624 | _erf_taylor(x - x_smallprec, x_smallprec, erf1, prec2).mult(1, prec) 625 | end 626 | 627 | # call-seq: 628 | # erfc(decimal, numeric) -> BigDecimal 629 | # 630 | # Computes the complementary error function of +decimal+ to the specified number of digits of 631 | # precision, +numeric+. 632 | # 633 | # If +decimal+ is NaN, returns NaN. 634 | # 635 | # BigMath.erfc(BigDecimal('10'), 32).to_s 636 | # #=> "0.20884875837625447570007862949578e-44" 637 | # 638 | def erfc(x, prec) 639 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :erfc) 640 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :erfc) 641 | return BigDecimal::Internal.nan_computation_result if x.nan? 642 | return BigDecimal(1 - x.infinite?) if x.infinite? 643 | return BigDecimal(1).sub(erf(x, prec + BigDecimal.double_fig), prec) if x < 0 644 | return BigDecimal(0) if x > 5000000000 # erfc(5000000000) < 1e-10000000000000000000 (underflow) 645 | 646 | if x >= 8 647 | y = _erfc_asymptotic(x, prec) 648 | return y.mult(1, prec) if y 649 | end 650 | 651 | # erfc(x) = 1 - erf(x) < exp(-x**2)/x/sqrt(pi) 652 | # Precision of erf(x) needs about log10(exp(-x**2)) extra digits 653 | log10 = 2.302585092994046 654 | high_prec = prec + BigDecimal.double_fig + (x.ceil**2 / log10).ceil 655 | BigDecimal(1).sub(erf(x, high_prec), prec) 656 | end 657 | 658 | # Calculates erf(x + a) 659 | private_class_method def _erf_taylor(x, a, erf_a, prec) # :nodoc: 660 | return erf_a if x.zero? 661 | # Let f(x+a) = erf(x+a)*exp((x+a)**2)*sqrt(pi)/2 662 | # = c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ... 663 | # f'(x+a) = 1+2*(x+a)*f(x+a) 664 | # f'(x+a) = c1 + 2*c2*x + 3*c3*x**2 + 4*c4*x**3 + 5*c5*x**4 + ... 665 | # = 1+2*(x+a)*(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + ...) 666 | # therefore, 667 | # c0 = f(a) 668 | # c1 = 2 * a * c0 + 1 669 | # c2 = (2 * c0 + 2 * a * c1) / 2 670 | # c3 = (2 * c1 + 2 * a * c2) / 3 671 | # c4 = (2 * c2 + 2 * a * c3) / 4 672 | # 673 | # All coefficients are positive when a >= 0 674 | 675 | scale = BigDecimal(2).div(sqrt(PI(prec), prec), prec) 676 | c_prev = erf_a.div(scale.mult(exp(-a*a, prec), prec), prec) 677 | c_next = (2 * a * c_prev).add(1, prec).mult(x, prec) 678 | sum = c_prev.add(c_next, prec) 679 | 680 | 2.step do |k| 681 | c = (c_prev.mult(x, prec) + a * c_next).mult(2, prec).mult(x, prec).div(k, prec) 682 | sum = sum.add(c, prec) 683 | c_prev, c_next = c_next, c 684 | break if [c_prev, c_next].all? { |c| c.zero? || (c.exponent < sum.exponent - prec) } 685 | end 686 | value = sum.mult(scale.mult(exp(-(x + a).mult(x + a, prec), prec), prec), prec) 687 | value > 1 ? BigDecimal(1) : value 688 | end 689 | 690 | private_class_method def _erfc_asymptotic(x, prec) # :nodoc: 691 | # Let f(x) = erfc(x)*sqrt(pi)*exp(x**2)/2 692 | # f(x) satisfies the following differential equation: 693 | # 2*x*f(x) = f'(x) + 1 694 | # From the above equation, we can derive the following asymptotic expansion: 695 | # f(x) = (0..kmax).sum { (-1)**k * (2*k)! / 4**k / k! / x**(2*k)) } / x 696 | 697 | # This asymptotic expansion does not converge. 698 | # But if there is a k that satisfies (2*k)! / 4**k / k! / x**(2*k) < 10**(-prec), 699 | # It is enough to calculate erfc within the given precision. 700 | # Using Stirling's approximation, we can simplify this condition to: 701 | # sqrt(2)/2 + k*log(k) - k - 2*k*log(x) < -prec*log(10) 702 | # and the left side is minimized when k = x**2. 703 | prec += BigDecimal.double_fig 704 | xf = x.to_f 705 | kmax = (1..(xf ** 2).floor).bsearch do |k| 706 | Math.log(2) / 2 + k * Math.log(k) - k - 2 * k * Math.log(xf) < -prec * Math.log(10) 707 | end 708 | return unless kmax 709 | 710 | sum = BigDecimal(1) 711 | x2 = x.mult(x, prec) 712 | d = BigDecimal(1) 713 | (1..kmax).each do |k| 714 | d = d.div(x2, prec).mult(1 - 2 * k, prec).div(2, prec) 715 | sum = sum.add(d, prec) 716 | end 717 | sum.div(exp(x2, prec).mult(PI(prec).sqrt(prec), prec), prec).div(x, prec) 718 | end 719 | 720 | # call-seq: 721 | # BigMath.gamma(decimal, numeric) -> BigDecimal 722 | # 723 | # Computes the gamma function of +decimal+ to the specified number of 724 | # digits of precision, +numeric+. 725 | # 726 | # BigMath.gamma(BigDecimal('0.5'), 32).to_s 727 | # #=> "0.17724538509055160272981674833411e1" 728 | # 729 | def gamma(x, prec) 730 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :gamma) 731 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma) 732 | prec2 = prec + BigDecimal.double_fig 733 | if x < 0.5 734 | raise Math::DomainError, 'Numerical argument is out of domain - gamma' if x.frac.zero? 735 | 736 | # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) 737 | pi = PI(prec2) 738 | sin = _sinpix(x, pi, prec2) 739 | return pi.div(gamma(1 - x, prec2).mult(sin, prec2), prec) 740 | elsif x.frac.zero? && x < 1000 * prec 741 | return _gamma_positive_integer(x, prec2).mult(1, prec) 742 | end 743 | 744 | a, sum = _gamma_spouge_sum_part(x, prec2) 745 | (x + (a - 1)).power(x - 0.5, prec2).mult(BigMath.exp(1 - x, prec2), prec2).mult(sum, prec) 746 | end 747 | 748 | # call-seq: 749 | # BigMath.lgamma(decimal, numeric) -> [BigDecimal, Integer] 750 | # 751 | # Computes the natural logarithm of the absolute value of the gamma function 752 | # of +decimal+ to the specified number of digits of precision, +numeric+ and its sign. 753 | # 754 | # BigMath.lgamma(BigDecimal('0.5'), 32) 755 | # #=> [0.57236494292470008707171367567653e0, 1] 756 | # 757 | def lgamma(x, prec) 758 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :lgamma) 759 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :lgamma) 760 | prec2 = prec + BigDecimal.double_fig 761 | if x < 0.5 762 | return [BigDecimal::INFINITY, 1] if x.frac.zero? 763 | 764 | loop do 765 | # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) 766 | pi = PI(prec2) 767 | sin = _sinpix(x, pi, prec2) 768 | log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec2).first + BigMath.log(sin.abs, prec2), prec) 769 | return [log_gamma, sin > 0 ? 1 : -1] if prec2 + log_gamma.exponent > prec + BigDecimal.double_fig 770 | 771 | # Retry with higher precision if loss of significance is too large 772 | prec2 = prec2 * 3 / 2 773 | end 774 | elsif x.frac.zero? && x < 1000 * prec 775 | log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) 776 | [log_gamma, 1] 777 | else 778 | # if x is close to 1 or 2, increase precision to reduce loss of significance 779 | diff1_exponent = (x - 1).exponent 780 | diff2_exponent = (x - 2).exponent 781 | extra_prec = [-diff1_exponent, -diff2_exponent, 0].max 782 | extremely_near_one = diff1_exponent < -prec2 783 | extremely_near_two = diff2_exponent < -prec2 784 | 785 | if extremely_near_one || extremely_near_two 786 | # If x is extreamely close to base = 1 or 2, linear interpolation is accurate enough. 787 | # Taylor expansion at x = base is: (x - base) * digamma(base) + (x - base) ** 2 * trigamma(base) / 2 + ... 788 | # And we can ignore (x - base) ** 2 and higher order terms. 789 | base = extremely_near_one ? 1 : 2 790 | d = BigDecimal(1)._decimal_shift(1 - prec2) 791 | log_gamma_d, sign = lgamma(base + d, prec2) 792 | return [log_gamma_d.mult(x - base, prec2).div(d, prec), sign] 793 | end 794 | 795 | prec2 += [-diff1_exponent, -diff2_exponent, 0].max 796 | a, sum = _gamma_spouge_sum_part(x, prec2) 797 | log_gamma = BigMath.log(sum, prec2).add((x - 0.5).mult(BigMath.log(x.add(a - 1, prec2), prec2), prec2) + 1 - x, prec) 798 | [log_gamma, 1] 799 | end 800 | end 801 | 802 | # Returns sum part: sqrt(2*pi) and c[k]/(x+k) terms of Spouge's approximation 803 | private_class_method def _gamma_spouge_sum_part(x, prec) # :nodoc: 804 | x -= 1 805 | # Spouge's approximation 806 | # x! = (x + a)**(x + 0.5) * exp(-x - a) * (sqrt(2 * pi) + (1..a - 1).sum{|k| c[k] / (x + k) } + epsilon) 807 | # where c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(a - k) / (k - 1)! 808 | # and epsilon is bounded by a**(-0.5) * (2 * pi) ** (-a - 0.5) 809 | 810 | # Estimate required a for given precision 811 | a = (prec / Math.log10(2 * Math::PI)).ceil 812 | 813 | # Calculate exponent of c[k] in low precision to estimate required precision 814 | low_prec = 16 815 | log10f = Math.log(10) 816 | x_low_prec = x.mult(1, low_prec) 817 | loggamma_k = 0 818 | ck_exponents = (1..a-1).map do |k| 819 | loggamma_k += Math.log10(k - 1) if k > 1 820 | -loggamma_k - k / log10f + (k - 0.5) * Math.log10(a - k) - BigMath.log10(x_low_prec.add(k, low_prec), low_prec) 821 | end 822 | 823 | # Estimate exponent of sum by Stirling's approximation 824 | approx_sum_exponent = x < 1 ? -Math.log10(a) / 2 : Math.log10(2 * Math::PI) / 2 + x_low_prec.add(0.5, low_prec) * Math.log10(x_low_prec / x_low_prec.add(a, low_prec)) 825 | 826 | # Determine required precision of c[k] 827 | prec2 = [ck_exponents.max.ceil - approx_sum_exponent.floor, 0].max + prec 828 | 829 | einv = BigMath.exp(-1, prec2) 830 | sum = (PI(prec) * 2).sqrt(prec).mult(BigMath.exp(-a, prec), prec) 831 | y = BigDecimal(1) 832 | (1..a - 1).each do |k| 833 | # c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(-k) / (k-1)! / (x + k) 834 | y = y.div(1 - k, prec2) if k > 1 835 | y = y.mult(einv, prec2) 836 | z = y.mult(BigDecimal((a - k) ** k), prec2).div(BigDecimal(a - k).sqrt(prec2).mult(x.add(k, prec2), prec2), prec2) 837 | # sum += c[k] / (x + k) 838 | sum = sum.add(z, prec2) 839 | end 840 | [a, sum] 841 | end 842 | 843 | private_class_method def _gamma_positive_integer(x, prec) # :nodoc: 844 | return x if x == 1 845 | numbers = (1..x - 1).map {|i| BigDecimal(i) } 846 | while numbers.size > 1 847 | numbers = numbers.each_slice(2).map {|a, b| b ? a.mult(b, prec) : a } 848 | end 849 | numbers.first 850 | end 851 | 852 | # Returns sin(pi * x), for gamma reflection formula calculation 853 | private_class_method def _sinpix(x, pi, prec) # :nodoc: 854 | x = x % 2 855 | sign = x > 1 ? -1 : 1 856 | x %= 1 857 | x = 1 - x if x > 0.5 # to avoid sin(pi*x) loss of precision for x close to 1 858 | sign * sin(x.mult(pi, prec), prec) 859 | end 860 | 861 | # call-seq: 862 | # frexp(x) -> [BigDecimal, Integer] 863 | # 864 | # Decomposes +x+ into a normalized fraction and an integral power of ten. 865 | # 866 | # BigMath.frexp(BigDecimal(123.456)) 867 | # #=> [0.123456e0, 3] 868 | # 869 | def frexp(x) 870 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, 0, :frexp) 871 | return [x, 0] unless x.finite? 872 | 873 | exponent = x.exponent 874 | [x._decimal_shift(-exponent), exponent] 875 | end 876 | 877 | # call-seq: 878 | # ldexp(fraction, exponent) -> BigDecimal 879 | # 880 | # Inverse of +frexp+. 881 | # Returns the value of fraction * 10**exponent. 882 | # 883 | # BigMath.ldexp(BigDecimal("0.123456e0"), 3) 884 | # #=> 0.123456e3 885 | # 886 | def ldexp(x, exponent) 887 | x = BigDecimal::Internal.coerce_to_bigdecimal(x, 0, :ldexp) 888 | x.finite? ? x._decimal_shift(exponent) : x 889 | end 890 | 891 | # call-seq: 892 | # PI(numeric) -> BigDecimal 893 | # 894 | # Computes the value of pi to the specified number of digits of precision, 895 | # +numeric+. 896 | # 897 | # BigMath.PI(32).to_s 898 | # #=> "0.31415926535897932384626433832795e1" 899 | # 900 | def PI(prec) 901 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :PI) 902 | n = prec + BigDecimal.double_fig 903 | zero = BigDecimal("0") 904 | one = BigDecimal("1") 905 | two = BigDecimal("2") 906 | 907 | m25 = BigDecimal("-0.04") 908 | m57121 = BigDecimal("-57121") 909 | 910 | pi = zero 911 | 912 | d = one 913 | k = one 914 | t = BigDecimal("-80") 915 | while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) 916 | m = BigDecimal.double_fig if m < BigDecimal.double_fig 917 | t = t*m25 918 | d = t.div(k,m) 919 | k = k+two 920 | pi = pi + d 921 | end 922 | 923 | d = one 924 | k = one 925 | t = BigDecimal("956") 926 | while d.nonzero? && ((m = n - (pi.exponent - d.exponent).abs) > 0) 927 | m = BigDecimal.double_fig if m < BigDecimal.double_fig 928 | t = t.div(m57121,n) 929 | d = t.div(k,m) 930 | pi = pi + d 931 | k = k+two 932 | end 933 | pi.mult(1, prec) 934 | end 935 | 936 | # call-seq: 937 | # E(numeric) -> BigDecimal 938 | # 939 | # Computes e (the base of natural logarithms) to the specified number of 940 | # digits of precision, +numeric+. 941 | # 942 | # BigMath.E(32).to_s 943 | # #=> "0.27182818284590452353602874713527e1" 944 | # 945 | def E(prec) 946 | prec = BigDecimal::Internal.coerce_validate_prec(prec, :E) 947 | exp(1, prec) 948 | end 949 | end 950 | --------------------------------------------------------------------------------