long-decimal 1.00.01 → 1.00.02
Sign up to get free protection for your applications and to get access to all the features.
- 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}
|