long-decimal 1.00.01 → 1.00.02
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +15 -0
- data/README +41 -41
- data/Rakefile +19 -73
- data/lib/long-decimal-extra.rb +5 -672
- data/lib/long-decimal.rb +1609 -141
- data/lib/long-decimal/version.rb +3 -0
- data/long-decimal.gemspec +22 -0
- data/test/testlongdecimal.rb +2498 -651
- data/test/testlongdeclib.rb +20 -10
- data/test/testrandlib.rb +6 -5
- data/test/testrandom.rb +30 -14
- data/test/testrandpower.rb +22 -7
- data/tex/long-decimal.pdf +0 -0
- data/tex/long-decimal.tex +420 -0
- metadata +72 -64
- data/VERSION +0 -1
- data/install.rb +0 -18
- data/make_doc.rb +0 -12
- data/test/testlongdecimal-extra.rb +0 -1750
- data/test/testlongdecimal-performance.rb +0 -357
- data/test/testrandom-extra.rb +0 -80
- data/version.rb +0 -39
data/test/testlongdeclib.rb
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
#
|
5
5
|
# (C) Karl Brodowsky (IT Sky Consulting GmbH) 2006-2009
|
6
6
|
#
|
7
|
-
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testlongdeclib.rb,v 1.
|
8
|
-
# CVS-Label: $Name:
|
7
|
+
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testlongdeclib.rb,v 1.42 2011/02/03 00:22:39 bk1 Exp $
|
8
|
+
# CVS-Label: $Name: $
|
9
9
|
# Author: $Author: bk1 $ (Karl Brodowsky)
|
10
10
|
#
|
11
11
|
|
@@ -21,13 +21,12 @@ class Integer
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
24
|
#
|
26
25
|
# test class for LongDecimal and LongDecimalQuot
|
27
26
|
#
|
28
27
|
module TestLongDecHelper
|
29
28
|
|
30
|
-
@RCS_ID='-$Id: testlongdeclib.rb,v 1.
|
29
|
+
@RCS_ID='-$Id: testlongdeclib.rb,v 1.42 2011/02/03 00:22:39 bk1 Exp $-'
|
31
30
|
|
32
31
|
def assert_equal_float(lhs, rhs, delta=0, msg="")
|
33
32
|
if ((lhs - rhs).abs >= delta)
|
@@ -37,11 +36,13 @@ module TestLongDecHelper
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def assert_equal_rbo(lhs, rhs, msg="", lhsname="lhs", rhsname="rhs", delta=0)
|
40
|
-
msg2 = "#{lhsname}=#{lhs} #{rhsname}=#{rhs} " + msg
|
39
|
+
msg2 = "#{lhsname}=#{lhs} (#{lhs.class}) #{rhsname}=#{rhs} (#{rhs.class}) " + msg
|
41
40
|
if (lhs.kind_of? Rational) && (rhs.kind_of? BigDecimal) || (lhs.kind_of? BigDecimal) && (rhs.kind_of? Rational)
|
42
41
|
assert_equal(lhs.to_ld, rhs.to_ld, msg2)
|
43
42
|
elsif (delta > 0 && ((lhs.kind_of? Float) || (rhs.kind_of? Float)))
|
44
43
|
assert_equal_float(lhs, rhs, delta, msg2 + " d=#{delta}")
|
44
|
+
elsif ((lhs.kind_of? Rational) && (rhs.kind_of? Rational))
|
45
|
+
assert_equal(lhs.numerator*rhs.denominator, lhs.denominator*rhs.numerator, msg)
|
45
46
|
else
|
46
47
|
assert_equal(lhs, rhs, msg2)
|
47
48
|
end
|
@@ -499,6 +500,11 @@ module TestLongDecHelper
|
|
499
500
|
y = y.to_ld
|
500
501
|
# calculate z = x**y
|
501
502
|
z = LongMath.power(x, y, prec)
|
503
|
+
prec_dp = 2*prec+1
|
504
|
+
z_dp = LongMath.power(x, y, prec_dp)
|
505
|
+
msg = "x=#{x}\ny=#{y}\nz=#{z}\nz_dp=#{z_dp}\nprec=#{prec}"
|
506
|
+
puts msg
|
507
|
+
assert((z - z_dp).abs <= 2*z.unit, msg)
|
502
508
|
|
503
509
|
corr2 = (x - 1).abs*1000000000 # 10**9
|
504
510
|
if (z.abs < LongMath::MAX_FLOATABLE && corr2 > 1)
|
@@ -509,23 +515,23 @@ module TestLongDecHelper
|
|
509
515
|
zf = z.to_f
|
510
516
|
qf = 1e9
|
511
517
|
delta = [ z.unit.to_f, zf.abs / qf ].max
|
512
|
-
# puts "delta=#{delta} z=#{z} zu=#{z.unit} zuf=#{z.unit.to_f} zf=#{zf} |zf/qf|=#{zf.abs/qf}"
|
513
518
|
if (yf.abs > 1)
|
514
519
|
l = Math.log(yf.abs)
|
515
520
|
if (l > 1)
|
516
521
|
delta *= l
|
517
522
|
end
|
518
|
-
# puts "delta=#{delta} l=#{l}"
|
519
523
|
end
|
520
524
|
corr = corr2 * 0.5
|
521
525
|
if corr > 1
|
522
526
|
corr_f = [ corr.to_f, 5.0 ].min
|
523
527
|
delta *= corr_f
|
524
528
|
end
|
525
|
-
# puts "delta=#{delta} corr_f=#{corr_f} corr=#{corr}"
|
526
529
|
|
527
530
|
diff = (zf - wf).abs
|
528
|
-
|
531
|
+
msg = "z=#{z}=#{zf} and wf=#{wf.to_s} should be almost equal\nx=#{x}=#{xf}\ny=#{y}=#{yf}\ndelta=#{delta}\nl=#{l}\ndiff=#{diff}\nprec=#{prec}\ncorr=#{corr}=#{corr.to_f}\ncorr2=#{corr2}=#{corr2.to_f}\ncorr_f=#{corr_f}"
|
532
|
+
puts msg
|
533
|
+
assert_equal_float(zf, wf, delta, msg)
|
534
|
+
puts "OK"
|
529
535
|
end
|
530
536
|
|
531
537
|
# check by taking log(z) = y * log(x)
|
@@ -549,9 +555,14 @@ module TestLongDecHelper
|
|
549
555
|
if (y.abs > 1) then
|
550
556
|
l10y = (Math.log(y.abs.to_f) / Math.log(10)).ceil
|
551
557
|
end
|
558
|
+
lprec_dp = 2*lprec+1
|
552
559
|
u = LongMath.log(z, lprec)
|
560
|
+
u_dp = LongMath.log(z_dp, lprec_dp)
|
553
561
|
v = LongMath.log(x, lprec+l10y)
|
562
|
+
v_dp = LongMath.log(x, lprec_dp + l10y)
|
554
563
|
yv = (y*v).round_to_scale(lprec, LongDecimal::ROUND_HALF_DOWN)
|
564
|
+
yv_dp = (y * v_dp).round_to_scale(lprec_dp, LongDecimal::ROUND_HALF_DOWN)
|
565
|
+
assert((u_dp - yv_dp).abs <= unit, "u=log(z,#{lprec})=#{u_dp}=#{u} and yv=y*v=y*log(x,#{lprec+l10y})=#{yv_dp}=#{yv} should be almost equal (unit=#{unit} x=#{x.to_s} y=#{y.to_s} z=#{z_dp}=#{z.to_s} u=#{u_dp}=#{u.to_s} v=#{v_dp}=#{v.to_s} lprec=#{lprec} prec=#{prec} lprec_dp=#{lprec_dp} prec_dp=#{prec_dp})")
|
555
566
|
assert((u - yv).abs <= unit, "u=log(z,#{lprec})=#{u} and yv=y*v=y*log(x,#{lprec+l10y})=#{yv} should be almost equal (unit=#{unit} x=#{x.to_s} y=#{y.to_s} z=#{z.to_s} u=#{u.to_s} v=#{v.to_s} lprec=#{lprec} prec=#{prec})")
|
556
567
|
end
|
557
568
|
|
@@ -812,7 +823,6 @@ module TestLongDecHelper
|
|
812
823
|
def check_cbrtb_with_remainder(x, s)
|
813
824
|
y, r = LongMath.cbrtb_with_remainder(x)
|
814
825
|
z0 = y.cube
|
815
|
-
# puts "x=#{x} y=#{y} z0=#{z0} r=#{r}"
|
816
826
|
z1 = z0 + r
|
817
827
|
z2 = (y+1).cube
|
818
828
|
assert(0 <= y, "cbrt _with_remainder must be >= 0" + s)
|
data/test/testrandlib.rb
CHANGED
@@ -4,20 +4,21 @@
|
|
4
4
|
#
|
5
5
|
# (C) Karl Brodowsky (IT Sky Consulting GmbH) 2006-2009
|
6
6
|
#
|
7
|
-
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testrandlib.rb,v 1.
|
8
|
-
# CVS-Label: $Name:
|
7
|
+
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testrandlib.rb,v 1.10 2011/02/03 00:22:39 bk1 Exp $
|
8
|
+
# CVS-Label: $Name: $
|
9
9
|
# Author: $Author: bk1 $ (Karl Brodowsky)
|
10
10
|
#
|
11
11
|
|
12
|
-
|
13
|
-
require "crypt/ISAAC"
|
12
|
+
require "rubygems"
|
13
|
+
# require "crypt/ISAAC"
|
14
|
+
require "crypt-isaac"
|
14
15
|
|
15
16
|
#
|
16
17
|
# test class for LongDecimal and LongDecimalQuot
|
17
18
|
#
|
18
19
|
module TestRandomHelper
|
19
20
|
|
20
|
-
@RCS_ID='-$Id: testrandlib.rb,v 1.
|
21
|
+
@RCS_ID='-$Id: testrandlib.rb,v 1.10 2011/02/03 00:22:39 bk1 Exp $-'
|
21
22
|
|
22
23
|
@@r1 = Crypt::ISAAC.new
|
23
24
|
@@r2 = Crypt::ISAAC.new
|
data/test/testrandom.rb
CHANGED
@@ -4,17 +4,24 @@
|
|
4
4
|
#
|
5
5
|
# (C) Karl Brodowsky (IT Sky Consulting GmbH) 2006-2009
|
6
6
|
#
|
7
|
-
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testrandom.rb,v 1.
|
8
|
-
# CVS-Label: $Name:
|
7
|
+
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testrandom.rb,v 1.18 2011/02/03 00:22:39 bk1 Exp $
|
8
|
+
# CVS-Label: $Name: $
|
9
9
|
# Author: $Author: bk1 $ (Karl Brodowsky)
|
10
10
|
#
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
require
|
12
|
+
$test_type = nil
|
13
|
+
if ((RUBY_VERSION.match /^1\./) || (RUBY_VERSION.match /^2\.0/)) then
|
14
|
+
require 'test/unit'
|
15
|
+
$test_type = :v20
|
16
|
+
else
|
17
|
+
require 'minitest/autorun'
|
18
|
+
require 'test/unit/assertions'
|
19
|
+
include Test::Unit::Assertions
|
20
|
+
$test_type = :v21
|
21
|
+
end
|
15
22
|
|
16
|
-
|
17
|
-
require "crypt
|
23
|
+
require "rubygems"
|
24
|
+
require "crypt-isaac"
|
18
25
|
|
19
26
|
load "lib/long-decimal.rb"
|
20
27
|
load "test/testlongdeclib.rb"
|
@@ -22,14 +29,23 @@ load "test/testrandlib.rb"
|
|
22
29
|
|
23
30
|
LongMath.prec_overflow_handling = :warn_use_max
|
24
31
|
|
32
|
+
if ($test_type == :v20)
|
33
|
+
class UnitTest < Test::Unit::TestCase
|
34
|
+
end
|
35
|
+
else
|
36
|
+
class UnitTest < MiniTest::Test
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
25
40
|
#
|
26
41
|
# test class for LongDecimal and LongDecimalQuot
|
27
42
|
#
|
28
|
-
class TestRandom_class <
|
43
|
+
class TestRandom_class < UnitTest
|
29
44
|
include TestLongDecHelper
|
30
45
|
include TestRandomHelper
|
46
|
+
include LongDecimalRoundingMode
|
31
47
|
|
32
|
-
@RCS_ID='-$Id: testrandom.rb,v 1.
|
48
|
+
@RCS_ID='-$Id: testrandom.rb,v 1.18 2011/02/03 00:22:39 bk1 Exp $-'
|
33
49
|
|
34
50
|
# for how many seconds should this test run? change to different
|
35
51
|
# value on demand
|
@@ -40,14 +56,14 @@ class TestRandom_class < RUNIT::TestCase
|
|
40
56
|
@scnt += 1
|
41
57
|
puts("\ncnt=#{cnt} scnt=#{@scnt} x=#{x} ep=#{eprec} lp=#{lprec} sp=#{sprec} pp=#{pprec}\n")
|
42
58
|
if (x <= LongMath::MAX_EXP_ABLE) then
|
43
|
-
|
59
|
+
check_exp_floated(x, eprec)
|
44
60
|
end
|
45
61
|
if (x > 0)
|
46
|
-
|
62
|
+
check_log_floated(x, lprec)
|
47
63
|
end
|
48
64
|
if (x > 0)
|
49
|
-
|
50
|
-
|
65
|
+
xr = x.round_to_scale(sc, LongMath::ROUND_HALF_UP)
|
66
|
+
check_sqrt_with_remainder(xr, sprec, "x=#{x} p=#{sprec}")
|
51
67
|
end
|
52
68
|
end
|
53
69
|
end
|
@@ -71,6 +87,6 @@ class TestRandom_class < RUNIT::TestCase
|
|
71
87
|
|
72
88
|
end
|
73
89
|
|
74
|
-
RUNIT::CUI::TestRunner.run(TestRandom_class.suite)
|
90
|
+
# RUNIT::CUI::TestRunner.run(TestRandom_class.suite)
|
75
91
|
|
76
92
|
# end of file testrandom.rb
|
data/test/testrandpower.rb
CHANGED
@@ -5,14 +5,20 @@
|
|
5
5
|
# (C) Karl Brodowsky (IT Sky Consulting GmbH) 2006-2009
|
6
6
|
#
|
7
7
|
# CVS-ID: $Header: /var/cvs/long-decimal/long-decimal/test/testrandpower.rb,v 1.14 2009/05/09 15:37:00 bk1 Exp $
|
8
|
-
# CVS-Label: $Name:
|
8
|
+
# CVS-Label: $Name: $
|
9
9
|
# Author: $Author: bk1 $ (Karl Brodowsky)
|
10
10
|
#
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
require
|
15
|
-
|
12
|
+
$test_type = nil
|
13
|
+
if ((RUBY_VERSION.match /^1\./) || (RUBY_VERSION.match /^2\.0/)) then
|
14
|
+
require 'test/unit'
|
15
|
+
$test_type = :v20
|
16
|
+
else
|
17
|
+
require 'minitest/autorun'
|
18
|
+
require 'test/unit/assertions'
|
19
|
+
include Test::Unit::Assertions
|
20
|
+
$test_type = :v21
|
21
|
+
end
|
16
22
|
|
17
23
|
load "lib/long-decimal.rb"
|
18
24
|
load "lib/long-decimal-extra.rb"
|
@@ -22,12 +28,21 @@ load "test/testrandlib.rb"
|
|
22
28
|
|
23
29
|
LongMath.prec_overflow_handling = :warn_use_max
|
24
30
|
|
31
|
+
if ($test_type == :v20)
|
32
|
+
class UnitTest < Test::Unit::TestCase
|
33
|
+
end
|
34
|
+
else
|
35
|
+
class UnitTest < MiniTest::Test
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
25
39
|
#
|
26
40
|
# test class for LongDecimal and LongDecimalQuot
|
27
41
|
#
|
28
|
-
class TestRandomPower_class <
|
42
|
+
class TestRandomPower_class < UnitTest
|
29
43
|
include TestLongDecHelper
|
30
44
|
include TestRandomHelper
|
45
|
+
include LongDecimalRoundingMode
|
31
46
|
|
32
47
|
@RCS_ID='-$Id: testrandpower.rb,v 1.14 2009/05/09 15:37:00 bk1 Exp $-'
|
33
48
|
|
@@ -72,6 +87,6 @@ class TestRandomPower_class < RUNIT::TestCase
|
|
72
87
|
|
73
88
|
end
|
74
89
|
|
75
|
-
RUNIT::CUI::TestRunner.run(TestRandomPower_class.suite)
|
90
|
+
# RUNIT::CUI::TestRunner.run(TestRandomPower_class.suite)
|
76
91
|
|
77
92
|
# end of file testrandpower.rb
|
Binary file
|
@@ -0,0 +1,420 @@
|
|
1
|
+
% LaTeX
|
2
|
+
% $Header: /var/cvs/long-decimal/long-decimal/tex/long-decimal.tex,v 1.3 2011/02/09 22:51:52 bk1 Exp $
|
3
|
+
% $Name: $
|
4
|
+
%
|
5
|
+
\documentclass[10pt,a4paper]{article}
|
6
|
+
\usepackage{isodate}
|
7
|
+
\usepackage[T1]{fontenc}
|
8
|
+
\usepackage[latin1]{inputenc}
|
9
|
+
\usepackage{graphics}
|
10
|
+
\usepackage{amsmath,amssymb}
|
11
|
+
\usepackage[a4paper,margin=3cm,footskip=.5cm]{geometry}
|
12
|
+
|
13
|
+
\title{The Ruby library {\slshape long-decimal}}
|
14
|
+
\author{Karl Brodowsky\\
|
15
|
+
IT Sky Consulting GmbH\\
|
16
|
+
Switzerland}
|
17
|
+
|
18
|
+
\date{2011-02-06}
|
19
|
+
|
20
|
+
%\makeatletter
|
21
|
+
%\makeatother
|
22
|
+
|
23
|
+
\setlength{\topmargin}{0mm}
|
24
|
+
%\setlength{\leftmargin}{10mm}
|
25
|
+
\setlength{\oddsidemargin}{-11mm}
|
26
|
+
\setlength{\evensidemargin}{-11mm}
|
27
|
+
\setlength{\parindent}{0mm}
|
28
|
+
|
29
|
+
%\newfont{\ocrb}{ocrb10}
|
30
|
+
|
31
|
+
\def\ld{\mathrm l}
|
32
|
+
\def\ldq{\mathrm q}
|
33
|
+
|
34
|
+
\begin{document}
|
35
|
+
\sffamily
|
36
|
+
\maketitle
|
37
|
+
|
38
|
+
\begin{abstract}
|
39
|
+
|
40
|
+
The goal of the Ruby-library {\slshape long-decimal}\/ is to provide a new
|
41
|
+
numerical type {\slshape LongDecimal\/}\/ and arithmetic operations to deal
|
42
|
+
with this type.
|
43
|
+
|
44
|
+
\end{abstract}
|
45
|
+
|
46
|
+
\section{Introduction}
|
47
|
+
|
48
|
+
Ruby supports non-integral numbers with the built-in types {\slshape
|
49
|
+
Float\/} and {\slshape Rational\/} and {\slshape BigDecimal\/}.
|
50
|
+
While these are very useful for many purposes, the development of
|
51
|
+
finance application requires the availability of a type like
|
52
|
+
{\slshape LongDecimal\/}, that allows explicit control of the rounding and uses a
|
53
|
+
decimal representation for the fractional part. An instance of
|
54
|
+
{\slshape LongDecimal\/} can be represented as a pair of integral
|
55
|
+
numbers $(n, d)$, where the value of the number is $\frac{n}{10^d}$.
|
56
|
+
The ring-operations $+$, $-$ and $*$ can be trivially defined in a way
|
57
|
+
that does not loose any information (and thus does not require any
|
58
|
+
rounding).
|
59
|
+
|
60
|
+
Division requires some additional thought, because the exact quotient
|
61
|
+
of two {\slshape LongDecimal\/}s can very well be expressed as
|
62
|
+
Rational, but not always as {\slshape LongDecimal\/}. A supplementary
|
63
|
+
numeric type {\slshape LongDecimalQuot\/} has been introduced to
|
64
|
+
support storing pairs $(r, d)$, where $r$ is a rational number and $d$
|
65
|
+
is an estimation about the significant digits after the decimal point.
|
66
|
+
The represented value is the rational number $r$.
|
67
|
+
|
68
|
+
Calculation of roots and trancendental functions require additional
|
69
|
+
information as input parameters to control the number of digits after
|
70
|
+
the decimal point and the rounding rules for achieving the result.
|
71
|
+
|
72
|
+
In the remainder of this document we will write $\ld(n, d)$ for the
|
73
|
+
{\slshape LongDecimal\/}-number represented by the pair $(n, d)$ in
|
74
|
+
the manner described above and $\ldq(r, d)$ for the {\slshape
|
75
|
+
LongDecimalQuot\/}-number represented by the pair $(r, n)$. It
|
76
|
+
needs to be observed that the part $d$ in both cases carries
|
77
|
+
information about the precision of the number in terms of significant
|
78
|
+
digits after the decimal point.
|
79
|
+
|
80
|
+
\pagebreak
|
81
|
+
|
82
|
+
\section{Ring Operations}
|
83
|
+
|
84
|
+
The ring operations $+$, $-$ and $*$ and obvious derived operations
|
85
|
+
like negation are defined like this:
|
86
|
+
|
87
|
+
$$\bigwedge_{e,d \in \mathbb{N}_0} \bigwedge_{m, n \in \mathbb{Z}}
|
88
|
+
\ld(m,d) + \ld(n,e) = \ld(m \cdot 10^{\max(d, e)-d} + n \cdot 10^{\max(d, e)-e}, \max(d, e))$$
|
89
|
+
$$\bigwedge_{e,d \in \mathbb{N}_0} \bigwedge_{m, n \in \mathbb{Z}}
|
90
|
+
\ld(m,d) - \ld(n,e) = \ld(m \cdot 10^{\max(d, e)-d} - n \cdot 10^{\max(d, e)-e}, \max(d, e))$$
|
91
|
+
$$\bigwedge_{e,d \in \mathbb{N}_0} \bigwedge_{m, n \in \mathbb{Z}}
|
92
|
+
\ld(m,d) \cdot \ld(n,e) = \ld(m n, d+e)$$
|
93
|
+
|
94
|
+
\pagebreak
|
95
|
+
|
96
|
+
\section{Rounding Operations}
|
97
|
+
|
98
|
+
The library supports two rounding operations that can be applied both
|
99
|
+
to {\slshape LongDecimal\/} and {\slshape LongDecimalQuot\/}. In the
|
100
|
+
latter case it implies a conversion to {\slshape LongDecimal\/}. The
|
101
|
+
normal rounding function {\slshape round\_to\_scale\/} changes the
|
102
|
+
precision to the given value, while keeping the value expressed by the
|
103
|
+
number approximately the same.
|
104
|
+
|
105
|
+
The optional second parameter describes how rounding will be done, if the
|
106
|
+
process looses information. It defaults to {\slshape
|
107
|
+
ROUND\_UNNECESSARY\/}, in which case an exception is raised whenever
|
108
|
+
a value changing rounding process would be required.
|
109
|
+
|
110
|
+
The other rounding operations can be grouped into two groups:
|
111
|
+
ROUND\_UP, ROUND\_DOWN, ROUND\_CEILING and ROUND\_FLOOR already answer
|
112
|
+
the question of how to round completely because they use the values
|
113
|
+
that result from rounding as boundaries and obviously round these to
|
114
|
+
themselves.
|
115
|
+
|
116
|
+
The other rounding operations put a boundary somewhere in the middle
|
117
|
+
between allowed rounded values, but they leave the question of how to
|
118
|
+
round the boundary itself to be answered as well. This is expressed
|
119
|
+
by having a major rounding mode that defines where the boundaries lie
|
120
|
+
and a minor rounding mode that is applicable for values that lie
|
121
|
+
exactly on the boundary. In some cases this cannot happen, because
|
122
|
+
boundaries are irrational and {\slshape LongDecimal\/} (and {\slshape LongDecimalQuot\/}) can
|
123
|
+
only express (some) rational numbers, but for the sake of completeness
|
124
|
+
and applicability to other representations, that might allow some
|
125
|
+
irrational values, this is done in a uniform way. Since rounding
|
126
|
+
operations can also apply to internal intermediate results, it is a
|
127
|
+
good idea not to constrain their definition to rational numbers.
|
128
|
+
|
129
|
+
Major rounding modes are
|
130
|
+
|
131
|
+
\begin{tabular}{|l|p{100mm}|}
|
132
|
+
\hline
|
133
|
+
MAJOR\_UP & always round in such a way that the absolute value does not decrease.No minor rounding mode needed.\\
|
134
|
+
\hline
|
135
|
+
MAJOR\_DOWN & always round in such a way that the absolute value does not increase. No minor rounding mode needed.\\
|
136
|
+
\hline
|
137
|
+
MAJOR\_CEILING & always round in such a way that the rational value does not decrease. No minor rounding mode needed.\\
|
138
|
+
\hline
|
139
|
+
MAJOR\_FLOOR & always round in such a way that the rational value does not increase. No minor rounding mode needed.\\
|
140
|
+
\hline
|
141
|
+
MAJOR\_HALF & round to the nearest available value. The boundary is the arithmetic mean of the two adjacent available values.\\
|
142
|
+
\hline
|
143
|
+
MAJOR\_GEOMETRIC & round to one of the two adjacent available values $x,y$ and use their geometric mean $\sqrt{xy}$ as boundary. For negative $x,y$ use the negated square root instead.\\
|
144
|
+
\hline
|
145
|
+
MAJOR\_HARMONIC & round to one of the two adjacent available values $x,y$ and use their harmonic mean $\frac{2xy}{x+y}$ as boundary.\\
|
146
|
+
\hline
|
147
|
+
MAJOR\_QUADRATIC & round to one of the two adjacent available values $x,y$ and use their quadratic mean $\sqrt{\frac{x^2+y^2}{2}}$ as boundary. If $x,y\le 0$ use the negated square root instead.\\
|
148
|
+
\hline
|
149
|
+
MAJOR\_CUBIC & round to one of the two adjacent available values $x,y$ and use their cubic mean $\sqrt[3]{\frac{x^3+y^3}{2}}$ as boundary.\\
|
150
|
+
\hline
|
151
|
+
MAJOR\_UNNECESSARY & raise an exception if value changing rounding would be needed\\
|
152
|
+
\hline
|
153
|
+
\end{tabular}
|
154
|
+
|
155
|
+
\pagebreak
|
156
|
+
|
157
|
+
Minor rounding modes are
|
158
|
+
|
159
|
+
|
160
|
+
\begin{tabular}{|l|p{100mm}|}
|
161
|
+
\hline
|
162
|
+
{\bfseries rounding mode}&{\bfseries description}\\
|
163
|
+
\hline
|
164
|
+
MINOR\_UNUSED & no minor rounding mode applies, can only be combined with ROUND\_UP, ROUND\_DOWN, ROUND\_CEILING and ROUND\_FLOOR.\\
|
165
|
+
\hline
|
166
|
+
MINOR\_UP & round values exactly on the boundary by increasing the absolute value (away from zero).\\
|
167
|
+
\hline
|
168
|
+
MINOR\_DOWN & round values exactly on the boundary by decreasing the absolute value (towards zero).\\
|
169
|
+
\hline
|
170
|
+
MINOR\_CEILING & round values exactly on the boundary by increasing the rational value (towards $\infty$).\\
|
171
|
+
\hline
|
172
|
+
MINOR\_FLOOR & round values exactly on the boundary by decreasing the rational value (towards $-\infty$).\\
|
173
|
+
\hline
|
174
|
+
MINOR\_EVEN & round values exactly on the boundary by using the choice resulting in the rounded last digit to be even.\\
|
175
|
+
\hline
|
176
|
+
MINOR\_ODD & round values exactly on the boundary by using the choice resulting in the rounded last digit to be odd.\\
|
177
|
+
\hline
|
178
|
+
\end{tabular}
|
179
|
+
|
180
|
+
\pagebreak
|
181
|
+
|
182
|
+
These combine to rounding modes:
|
183
|
+
|
184
|
+
\begin{tabular}{|l|p{100mm}|}
|
185
|
+
\hline
|
186
|
+
{\bfseries rounding mode}&{\bfseries description}\\
|
187
|
+
\hline
|
188
|
+
ROUND\_UP & always round in such a way that the absolute value does not decrease.\\
|
189
|
+
\hline
|
190
|
+
ROUND\_DOWN & always round in such a way that the absolute value does not increase.\\
|
191
|
+
\hline
|
192
|
+
ROUND\_CEILING & always round in such a way that the rational value does not decrease.\\
|
193
|
+
\hline
|
194
|
+
ROUND\_FLOOR & always round in such a way that the rational value does not increase.\\
|
195
|
+
\hline
|
196
|
+
ROUND\_HALF\_UP & round to the nearest available value, prefer increasing the absolute value if the last digit is 5\\
|
197
|
+
\hline
|
198
|
+
ROUND\_HALF\_DOWN & round to the nearest available value, prefer decreasing the absolute value if the last digit is 5\\
|
199
|
+
\hline
|
200
|
+
ROUND\_HALF\_CEILING & round to the nearest available value, prefer increasing the rational value if the last digit is 5\\
|
201
|
+
\hline
|
202
|
+
ROUND\_HALF\_FLOOR & round to the nearest available value, prefer decreasing the rational value if the last digit is 5\\
|
203
|
+
\hline
|
204
|
+
ROUND\_HALF\_EVEN & round to the nearest available value, prefer the resulting last digit to be even if the last digit prior to rounding is 5\\
|
205
|
+
\hline
|
206
|
+
ROUND\_HALF\_ODD & round to the nearest available value, prefer the resulting last digit to be odd if the last digit prior to rounding is 5\\
|
207
|
+
\hline
|
208
|
+
ROUND\_GEOMETRIC\_UP & round to the available value using the geometric mean as boundary, prefer increasing the absolute value if the unrounded value is exactly on the boundary\\
|
209
|
+
\hline
|
210
|
+
ROUND\_GEOMETRIC\_DOWN & round to the available value, using the geometric mean as boundary, prefer decreasing the absolute value if the unrounded value is exactly on the boundary\\
|
211
|
+
\hline
|
212
|
+
\dots & \ldots\\
|
213
|
+
\hline
|
214
|
+
ROUND\_HARMONIC\_UP & round to the available value using the harmonic mean as boundary, prefer increasing the absolute value if the unrounded value is exactly on the boundary\\
|
215
|
+
\hline
|
216
|
+
ROUND\_HARMONIC\_DOWN & round to the available value, using the harmonic mean as boundary, prefer decreasing the absolute value if the unrounded value is exactly on the boundary\\
|
217
|
+
\hline
|
218
|
+
\dots & \ldots\\
|
219
|
+
\hline
|
220
|
+
ROUND\_QUADRATIC\_UP & round to the available value using the quadratic mean as boundary, prefer increasing the absolute value if the unrounded value is exactly on the boundary\\
|
221
|
+
\hline
|
222
|
+
ROUND\_QUADRATIC\_DOWN & round to the available value, using the quadratic mean as boundary, prefer decreasing the absolute value if the unrounded value is exactly on the boundary\\
|
223
|
+
\hline
|
224
|
+
\dots & \ldots\\
|
225
|
+
\hline
|
226
|
+
ROUND\_CUBIC\_UP & round to the available value using the cubic mean as boundary, prefer increasing the absolute value if the unrounded value is exactly on the boundary\\
|
227
|
+
\hline
|
228
|
+
ROUND\_CUBIC\_DOWN & round to the available value, using the cubic mean as boundary, prefer decreasing the absolute value if the unrounded value is exactly on the boundary\\
|
229
|
+
\hline
|
230
|
+
\dots & \ldots\\
|
231
|
+
\hline
|
232
|
+
ROUND\_CUBIC\_ODD & round to the available value using the cubic mean as boundary, prefer the resulting last digit to be odd if the unrounded value is exactly on the boundary\\
|
233
|
+
\hline
|
234
|
+
ROUND\_UNNECESSARY & raise an exception if value changing rounding would be needed\\
|
235
|
+
\hline
|
236
|
+
\end{tabular}
|
237
|
+
|
238
|
+
|
239
|
+
In addition to these commonly available rounding operations {\slshape long-decimal\/} provides rounding to remainder sets. This is motivated by the practice in some
|
240
|
+
currencies to prefer using certain multiples of the smallest unit. For example in CHF you commonly use two digits after the decimal point, but the last digit is
|
241
|
+
required to be 0 or 5. This is achieved as a special case of a more general concept {\slshape round\_to\_allowed\_remainders\/}. A modulus $M \ge 2$ and a set
|
242
|
+
$R\subset\mathbb{N}_0$ are given and a number $x$ is rounded to $\ld(n, d)$ such that
|
243
|
+
|
244
|
+
$$\bigvee_{r\in R} n \equiv r \mod M$$
|
245
|
+
|
246
|
+
Typically $M$ is ten or a power of ten, but this is not required. Neither is it required that zero is a member of $R$. In that case an additional parameter is needed
|
247
|
+
in order to define to which direction a potential last digit of zero would be rounded. In the CHF case we would have $M=10$ and $R=\{0, 5\}$.
|
248
|
+
|
249
|
+
This kind of rounding can be applied to integers as well as to
|
250
|
+
{\slshape LongDecimal\/} and {\slshape LongDecimalQuot\/}, in which case the integral numerator
|
251
|
+
$n$ of $\ld(n, d)=\frac{n}{10^d}$ must fullfill the additional
|
252
|
+
constraint. In order to avoid complications, this is not supported in
|
253
|
+
conjunction with minor rounding modes MINOR\_EVEN and MINOR\_ODD,
|
254
|
+
because all matching rounded values might be even or odd and we cannot
|
255
|
+
rely on the alternation of even and odd values.
|
256
|
+
It is possible to exclude $0$ from the set $R$. In this case a
|
257
|
+
ZERO-rounding-mode needs to be provided that tells in which way a
|
258
|
+
zero, should it occur, shoud be rounded.
|
259
|
+
|
260
|
+
\pagebreak
|
261
|
+
|
262
|
+
\section{Division}
|
263
|
+
|
264
|
+
Regular division using $/$ yields an instance of {\slshape LongDecimalQuot\/}. The approximate number of significant digits is estimated by using the partial derivatives of $f(x, y) = \frac{x}{y}$.
|
265
|
+
Assume $x=\ld(m, s)$ and $y=\ld(n, t)$. So we get for $\ld(m, s) / \ld(n, t)$ an result
|
266
|
+
|
267
|
+
$$\frac{x}{y} = \ldq(10^{t-s}\frac{m}{n}, r)$$
|
268
|
+
|
269
|
+
with
|
270
|
+
|
271
|
+
$$r \approx -\log_{10}\left(10^{-s} \frac{1}{|y|} + 10^{-t} \frac{|x|}{y^2}\right)
|
272
|
+
= 2 \log_{10}(y) + s + t -\log_{10}\left(|m| + |n|) \right)$$
|
273
|
+
|
274
|
+
In order to avoid expensive logarithmic operation for such basic operations as division this is approximated by
|
275
|
+
|
276
|
+
$$r = \max( 0, 2 v + s + t - \max( u + s, v + t) - 3)$$
|
277
|
+
with $u = \lfloor\log |m| \rfloor - s + 1$ and $v = \lfloor\log |n| \rfloor - t + 1$ for $m$ and $n$ not zero
|
278
|
+
and $u=-s$ for $m = 0$ and $v=-t$ for $n=0$. Using $\max(a, b)$ instead of $log_{10}(10^a + 10^b)$ is a good approximation, when a and b are far apart.
|
279
|
+
|
280
|
+
It is recommended to use explicit rounding after having performed
|
281
|
+
division rather than to rely on this somewhat arbitrary estimation on
|
282
|
+
the number of significant digits. It is possible to retain
|
283
|
+
intermediate results with {\slshape LongDecimalQuot\/}, because the
|
284
|
+
full arithmetic is available for this type as well, but using rational
|
285
|
+
numbers internally it will blow up numerator and denominator to an
|
286
|
+
extent that diminishes performance by quite a margin in longer
|
287
|
+
calculations.
|
288
|
+
|
289
|
+
\pagebreak
|
290
|
+
|
291
|
+
\section{Roots}
|
292
|
+
|
293
|
+
Square root and cube root of {\slshape LongDecimal\/} can be calculated quite
|
294
|
+
efficiently using an algorithm that is somewhat similar to the
|
295
|
+
algorithm for long integer division. This has been preferred over the
|
296
|
+
more commonly known Newton algorithm. Since square and cube roots are
|
297
|
+
usually irrational, it is mandatory to provide rounding information
|
298
|
+
concerning the number of desired digits and the rounding mode.
|
299
|
+
|
300
|
+
Square roots and cube roots can also calculated of integers, in which
|
301
|
+
case a variant is available that also calculates a remainder $r$ in
|
302
|
+
addition to the approximated square root $s$, such
|
303
|
+
that
|
304
|
+
$a = s^2+r$.
|
305
|
+
|
306
|
+
\section{$\pi$}
|
307
|
+
|
308
|
+
The number $\pi$ can be calculated to a given number of digits, which
|
309
|
+
works sufficiently fast for a few thousend digits.
|
310
|
+
|
311
|
+
Please use dedicated programs if you seriously want to calculate $\pi$
|
312
|
+
to millions of digits, Ruby is not fast enough for this kind of number
|
313
|
+
crunching to compete with the best C-programs.
|
314
|
+
|
315
|
+
\pagebreak
|
316
|
+
|
317
|
+
\section{Transcendental Functions}
|
318
|
+
|
319
|
+
It is the goal of this library to support the most common
|
320
|
+
transcendental functions in the long run. Currently $\exp$ and $\log$
|
321
|
+
are supported. These are calculated using the Taylor series, but the
|
322
|
+
calculation has been improved. Most important it is to improve the
|
323
|
+
convergence, which is the case with a series of the form
|
324
|
+
|
325
|
+
$$\sum_{n=0}^\infty \frac{x^n}{n!}$$
|
326
|
+
|
327
|
+
for $x< 0.5$.
|
328
|
+
|
329
|
+
For the exponential function this can always be achieved by using the following transformations: $x = 2^n x_0$ with $x_0 < 0.5$ implies
|
330
|
+
|
331
|
+
$$\exp(x) = \exp(x_0)^{2^n}$$
|
332
|
+
|
333
|
+
which can be easily calculated using successive squaring operations.
|
334
|
+
|
335
|
+
For the logarithm we first use $x = e^n x_0$ with $n \in \mathbb{N}_0$ and $x_0 < e$ with
|
336
|
+
|
337
|
+
$$\log(x) = n + \log(x_0).$$
|
338
|
+
|
339
|
+
From here we make use of the efficient square root calculation facility and use
|
340
|
+
|
341
|
+
$$\bigwedge_{m=0}^\infty x_{m+1} = \sqrt{x_m}$$
|
342
|
+
|
343
|
+
and
|
344
|
+
|
345
|
+
$$\log(x) = n + 2^m \log x_m$$
|
346
|
+
|
347
|
+
Using this $x_m$ can be brought sufficiently close to $1$ to make the Taylor series of $\log$ converge sufficiently fast to be useful for the calculation:
|
348
|
+
|
349
|
+
$$\log(x_m) = \sum_{k=1}^\infty \frac{(x_m-1)^k(-1)^{k+1}}{k}$$
|
350
|
+
|
351
|
+
Another minor optimization uses some integer $j$ and adds
|
352
|
+
|
353
|
+
$$ \bigwedge_{l=0}^{j-1} s_k = \sum_{l=0}^n \frac{x^{lj}}{(lj+k)!}$$
|
354
|
+
|
355
|
+
from which we finally multiply the partial sums with appropriate low powers of $x$. This saves on the number of multiplications.
|
356
|
+
Similar patterns will be applied to the calculation of other transcendental functions.
|
357
|
+
|
358
|
+
\pagebreak
|
359
|
+
|
360
|
+
\section{Powers}
|
361
|
+
|
362
|
+
Calculation of powers with any positive {\slshape LongDecimal\/} as base and any {\slshape LongDecimal\/} as exponent is quite challenging to do.
|
363
|
+
|
364
|
+
It is quite trivial that $x^y = \exp(y \log x)$, but the challenges
|
365
|
+
are to achieve the result in acceptable time and with the required
|
366
|
+
precision. The builtin class {\slshape BigDecimal} does not meet
|
367
|
+
these goals, because it becomes incredibly slow for certain
|
368
|
+
combinations of $x$ and $y$. The power function of {\slshape LongDecimal\/} has
|
369
|
+
been optimized to handle a broad range of cases with different
|
370
|
+
approaches to provide precision and speed. This should work fine for
|
371
|
+
reasonable practical use, but it will still be possible to construct
|
372
|
+
corner cases with bases very close to 1 and large exponents which fail
|
373
|
+
in an attempt to do an accurate calculation in their last digits.
|
374
|
+
|
375
|
+
\section{Means}
|
376
|
+
|
377
|
+
In the class {\slshape LongMath} there are methods for calculating the
|
378
|
+
following means: arithmetic\_mean, geometric\_mean, harmonic\_mean,
|
379
|
+
quadratic\_mean, cubic\_mean, arithmetic\_geometric\_mean,
|
380
|
+
harmonic\_geometric\_mean. See Wikipedia for their definitions.
|
381
|
+
|
382
|
+
\section{Rounding with sum constraint}
|
383
|
+
|
384
|
+
Experimental support for rounding of several number simultanously in
|
385
|
+
such a way that their rounded sum is the sum of the rounded numbers is
|
386
|
+
included with the methods {\slshape LongMath.round\_sum\_hm} which uses
|
387
|
+
the Haare-Niemeyer-approach and {\slshape LongMath.round\_sum\_divisor}
|
388
|
+
which uses one of several divisor based approaches, like D'Hondt. The
|
389
|
+
approach is chosen by the rounding mode. These are not yet implemented
|
390
|
+
efficiently nor are they tested well, so use at your own risk (like
|
391
|
+
the whole library).
|
392
|
+
|
393
|
+
\section{Limitations}
|
394
|
+
|
395
|
+
Rounding with sum constraint is not yet production stable.
|
396
|
+
|
397
|
+
Powers with bases that are off 1 by $10^{-20}$ or less and exponents
|
398
|
+
in the order of magnitude of $10^20$ or more are not always calculated
|
399
|
+
precisely.
|
400
|
+
|
401
|
+
Some interesting transcendental functions are missing.
|
402
|
+
|
403
|
+
Many operations (like power, exp, log and other transcendental functions) fail to work with numbers whose magnitude cannot be
|
404
|
+
expressed as double.
|
405
|
+
|
406
|
+
Using transcendental functions that have a rounding mode and precision
|
407
|
+
as part of their parameter set has not been tested with the newer
|
408
|
+
rounding modes, \dots\_ODD, ROUND\_GEOMETRIC\_\dots,
|
409
|
+
ROUND\_HARMONIC\_\dots, ROUND\_QUADRATIC\_\dots and
|
410
|
+
ROUND\_CUBIC\_\dots.
|
411
|
+
These combinations will be tested and improved in future versions.
|
412
|
+
|
413
|
+
\section{Tests}
|
414
|
+
|
415
|
+
Unit tests have been added to test much of the implemented
|
416
|
+
functionality.
|
417
|
+
|
418
|
+
More unit tests are desirable.
|
419
|
+
|
420
|
+
\end{document}
|