Skip to content

Commit 12e9241

Browse files
authored
Add implementation of natural logarithm (shopspring#339)
* Add initial implementation of natural logarithm * Add constApproxmation struct to represent mathematical constants with their approximations
1 parent 88705b7 commit 12e9241

File tree

4 files changed

+275
-0
lines changed

4 files changed

+275
-0
lines changed

const.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package decimal
2+
3+
import (
4+
"strings"
5+
)
6+
7+
const (
8+
strLn10 = "2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286248633409525465082806756666287369098781689482907208325554680843799894826233198528393505308965377732628846163366222287698219886746543667474404243274365155048934314939391479619404400222105101714174800368808401264708068556774321622835522011480466371565912137345074785694768346361679210180644507064800027750268491674655058685693567342067058113642922455440575892572420824131469568901675894025677631135691929203337658714166023010570308963457207544037084746994016826928280848118428931484852494864487192780967627127577539702766860595249671667418348570442250719796500471495105049221477656763693866297697952211071826454973477266242570942932258279850258550978526538320760672631716430950599508780752371033310119785754733154142180842754386359177811705430982748238504564801909561029929182431823752535770975053956518769751037497088869218020518933950723853920514463419726528728696511086257149219884997874887377134568620916705849807828059751193854445009978131146915934666241071846692310107598438319191292230792503747298650929009880391941702654416816335727555703151596113564846546190897042819763365836983716328982174407366009162177850541779276367731145041782137660111010731042397832521894898817597921798666394319523936855916447118246753245630912528778330963604262982153040874560927760726641354787576616262926568298704957954913954918049209069438580790032763017941503117866862092408537949861264933479354871737451675809537088281067452440105892444976479686075120275724181874989395971643105518848195288330746699317814634930000321200327765654130472621883970596794457943468343218395304414844803701305753674262153675579814770458031413637793236291560128185336498466942261465206459942072917119370602444929358037007718981097362533224548366988505528285966192805098447175198503666680874970496982273220244823343097169111136813588418696549323714996941979687803008850408979618598756579894836445212043698216415292987811742973332588607915912510967187510929248475023930572665446276200923068791518135803477701295593646298412366497023355174586195564772461857717369368404676577047874319780573853271810933883496338813069945569399346101090745616033312247949360455361849123333063704751724871276379140924398331810164737823379692265637682071706935846394531616949411701841938119405416449466111274712819705817783293841742231409930022911502362192186723337268385688273533371925103412930705632544426611429765388301822384091026198582888433587455960453004548370789052578473166283701953392231047527564998119228742789713715713228319641003422124210082180679525276689858180956119208391760721080919923461516952599099473782780648128058792731993893453415320185969711021407542282796298237068941764740642225757212455392526179373652434440560595336591539160312524480149313234572453879524389036839236450507881731359711238145323701508413491122324390927681724749607955799151363982881058285740538000653371655553014196332241918087621018204919492651483892"
9+
)
10+
11+
var (
12+
ln10 = newConstApproximation(strLn10)
13+
)
14+
15+
type constApproximation struct {
16+
exact Decimal
17+
approximations []Decimal
18+
}
19+
20+
func newConstApproximation(value string) constApproximation {
21+
parts := strings.Split(value, ".")
22+
coeff, fractional := parts[0], parts[1]
23+
24+
coeffLen := len(coeff)
25+
maxPrecision := len(fractional)
26+
27+
var approximations []Decimal
28+
for p := 1; p < maxPrecision; p *= 2 {
29+
r := RequireFromString(value[:coeffLen+p])
30+
approximations = append(approximations, r)
31+
}
32+
33+
return constApproximation{
34+
RequireFromString(value),
35+
approximations,
36+
}
37+
}
38+
39+
// Returns the smallest approximation available that's at least as precise
40+
// as the passed precision (places after decimal point), i.e. Floor[ log2(precision) ] + 1
41+
func (c constApproximation) withPrecision(precision int32) Decimal {
42+
i := 0
43+
44+
if precision >= 1 {
45+
i++
46+
}
47+
48+
for precision >= 16 {
49+
precision /= 16
50+
i += 4
51+
}
52+
53+
for precision >= 2 {
54+
precision /= 2
55+
i++
56+
}
57+
58+
if i >= len(c.approximations) {
59+
return c.exact
60+
}
61+
62+
return c.approximations[i]
63+
}

const_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package decimal
2+
3+
import "testing"
4+
5+
func TestConstApproximation(t *testing.T) {
6+
for _, testCase := range []struct {
7+
Const string
8+
Precision int32
9+
ExpectedApproximation string
10+
}{
11+
{"2.3025850929940456840179914546", 0, "2"},
12+
{"2.3025850929940456840179914546", 1, "2.3"},
13+
{"2.3025850929940456840179914546", 3, "2.302"},
14+
{"2.3025850929940456840179914546", 5, "2.302585"},
15+
{"2.3025850929940456840179914546", 10, "2.302585092994045"},
16+
{"2.3025850929940456840179914546", 100, "2.3025850929940456840179914546"},
17+
{"2.3025850929940456840179914546", -1, "2"},
18+
{"2.3025850929940456840179914546", -5, "2"},
19+
{"3.14159265359", 0, "3"},
20+
{"3.14159265359", 1, "3.1"},
21+
{"3.14159265359", 2, "3.141"},
22+
{"3.14159265359", 4, "3.1415926"},
23+
{"3.14159265359", 13, "3.14159265359"},
24+
} {
25+
ca := newConstApproximation(testCase.Const)
26+
expected, _ := NewFromString(testCase.ExpectedApproximation)
27+
28+
approximation := ca.withPrecision(testCase.Precision)
29+
30+
if approximation.Cmp(expected) != 0 {
31+
t.Errorf("expected approximation %s, got %s - for const with %s precision %d", testCase.ExpectedApproximation, approximation.String(), testCase.Const, testCase.Precision)
32+
}
33+
}
34+
}

decimal.go

+117
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,123 @@ func (d Decimal) ExpTaylor(precision int32) (Decimal, error) {
808808
return result, nil
809809
}
810810

811+
// Ln calculates natural logarithm of d.
812+
// Precision argument specifies how precise the result must be (number of digits after decimal point).
813+
// Negative precision is allowed.
814+
//
815+
// Example:
816+
//
817+
// d1, err := NewFromFloat(13.3).Ln(2)
818+
// d1.String() // output: "2.59"
819+
//
820+
// d2, err := NewFromFloat(579.161).Ln(10)
821+
// d2.String() // output: "6.3615805046"
822+
func (d Decimal) Ln(precision int32) (Decimal, error) {
823+
// Algorithm based on The Use of Iteration Methods for Approximating the Natural Logarithm,
824+
// James F. Epperson, The American Mathematical Monthly, Vol. 96, No. 9, November 1989, pp. 831-835.
825+
if d.IsNegative() {
826+
return Decimal{}, fmt.Errorf("cannot calculate natural logarithm for negative decimals")
827+
}
828+
829+
if d.IsZero() {
830+
return Decimal{}, fmt.Errorf("cannot represent natural logarithm of 0, result: -infinity")
831+
}
832+
833+
calcPrecision := precision + 2
834+
z := d.Copy()
835+
836+
var comp1, comp3, comp2, comp4, reduceAdjust Decimal
837+
comp1 = z.Sub(Decimal{oneInt, 0})
838+
comp3 = Decimal{oneInt, -1}
839+
840+
// for decimal in range [0.9, 1.1] where ln(d) is close to 0
841+
usePowerSeries := false
842+
843+
if comp1.Abs().Cmp(comp3) <= 0 {
844+
usePowerSeries = true
845+
} else {
846+
// reduce input decimal to range [0.1, 1)
847+
expDelta := int32(z.NumDigits()) + z.exp
848+
z.exp -= expDelta
849+
850+
// Input decimal was reduced by factor of 10^expDelta, thus we will need to add
851+
// ln(10^expDelta) = expDelta * ln(10)
852+
// to the result to compensate that
853+
ln10 := ln10.withPrecision(calcPrecision)
854+
reduceAdjust = NewFromInt32(expDelta)
855+
reduceAdjust = reduceAdjust.Mul(ln10)
856+
857+
comp1 = z.Sub(Decimal{oneInt, 0})
858+
859+
if comp1.Abs().Cmp(comp3) <= 0 {
860+
usePowerSeries = true
861+
} else {
862+
// initial estimate using floats
863+
zFloat := z.InexactFloat64()
864+
comp1 = NewFromFloat(math.Log(zFloat))
865+
}
866+
}
867+
868+
epsilon := Decimal{oneInt, -calcPrecision}
869+
870+
if usePowerSeries {
871+
// Power Series - https://en.wikipedia.org/wiki/Logarithm#Power_series
872+
// Calculating n-th term of formula: ln(z+1) = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ]
873+
// until the difference between current and next term is smaller than epsilon.
874+
// Coverage quite fast for decimals close to 1.0
875+
876+
// z + 2
877+
comp2 = comp1.Add(Decimal{twoInt, 0})
878+
// z / (z + 2)
879+
comp3 = comp1.DivRound(comp2, calcPrecision)
880+
// 2 * (z / (z + 2))
881+
comp1 = comp3.Add(comp3)
882+
comp2 = comp1.Copy()
883+
884+
for n := 1; ; n++ {
885+
// 2 * (z / (z+2))^(2n+1)
886+
comp2 = comp2.Mul(comp3).Mul(comp3)
887+
888+
// 1 / (2n+1) * 2 * (z / (z+2))^(2n+1)
889+
comp4 = NewFromInt(int64(2*n + 1))
890+
comp4 = comp2.DivRound(comp4, calcPrecision)
891+
892+
// comp1 = 2 sum [ 1 / (2n+1) * (z / (z+2))^(2n+1) ]
893+
comp1 = comp1.Add(comp4)
894+
895+
if comp4.Abs().Cmp(epsilon) <= 0 {
896+
break
897+
}
898+
}
899+
} else {
900+
// Halley's Iteration.
901+
// Calculating n-th term of formula: a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z),
902+
// until the difference between current and next term is smaller than epsilon
903+
for {
904+
// exp(a_n)
905+
comp3, _ = comp1.ExpTaylor(calcPrecision)
906+
// exp(a_n) - z
907+
comp2 = comp3.Sub(z)
908+
// 2 * (exp(a_n) - z)
909+
comp2 = comp2.Add(comp2)
910+
// exp(a_n) + z
911+
comp4 = comp3.Add(z)
912+
// 2 * (exp(a_n) - z) / (exp(a_n) + z)
913+
comp3 = comp2.DivRound(comp4, calcPrecision)
914+
// comp1 = a_(n+1) = a_n - 2 * (exp(a_n) - z) / (exp(a_n) + z)
915+
comp1 = comp1.Sub(comp3)
916+
917+
if comp3.Abs().Cmp(epsilon) <= 0 {
918+
break
919+
}
920+
}
921+
}
922+
923+
comp1 = comp1.Add(reduceAdjust)
924+
925+
return comp1.Round(precision), nil
926+
}
927+
811928
// NumDigits returns the number of digits of the decimal coefficient (d.Value)
812929
// Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part
813930
func (d Decimal) NumDigits() int {

decimal_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -2749,6 +2749,67 @@ func TestDecimal_ExpTaylor(t *testing.T) {
27492749
}
27502750
}
27512751

2752+
func TestDecimal_Ln(t *testing.T) {
2753+
for _, testCase := range []struct {
2754+
Dec string
2755+
Precision int32
2756+
Expected string
2757+
}{
2758+
{"0.1", 25, "-2.3025850929940456840179915"},
2759+
{"0.01", 25, "-4.6051701859880913680359829"},
2760+
{"0.001", 25, "-6.9077552789821370520539744"},
2761+
{"0.00000001", 25, "-18.4206807439523654721439316"},
2762+
{"1.0", 10, "0.0"},
2763+
{"1.01", 25, "0.0099503308531680828482154"},
2764+
{"1.001", 25, "0.0009995003330835331668094"},
2765+
{"1.0001", 25, "0.0000999950003333083353332"},
2766+
{"1.1", 25, "0.0953101798043248600439521"},
2767+
{"1.13", 25, "0.1222176327242492005461486"},
2768+
{"3.13", 10, "1.1410330046"},
2769+
{"3.13", 25, "1.1410330045520618486427824"},
2770+
{"3.13", 50, "1.14103300455206184864278239988848193837089629107972"},
2771+
{"3.13", 100, "1.1410330045520618486427823998884819383708962910797239760817078430268177216960996098918971117211892839"},
2772+
{"5.71", 25, "1.7422190236679188486939833"},
2773+
{"5.7185108151957193571930205", 50, "1.74370842450178929149992165925283704012576949094645"},
2774+
{"839101.0351", 25, "13.6400864014410013994397240"},
2775+
{"839101.0351094726488848490572028502", 50, "13.64008640145229044389152437468283605382056561604272"},
2776+
{"5023583755703750094849.03519358513093500275017501750602739169823", 25, "49.9684305274348922267409953"},
2777+
{"5023583755703750094849.03519358513093500275017501750602739169823", -1, "50.0"},
2778+
} {
2779+
d, _ := NewFromString(testCase.Dec)
2780+
expected, _ := NewFromString(testCase.Expected)
2781+
2782+
ln, err := d.Ln(testCase.Precision)
2783+
if err != nil {
2784+
t.Fatal(err)
2785+
}
2786+
2787+
if ln.Cmp(expected) != 0 {
2788+
t.Errorf("expected %s, got %s, for decimal %s", testCase.Expected, ln.String(), testCase.Dec)
2789+
}
2790+
}
2791+
}
2792+
2793+
func TestDecimal_LnZero(t *testing.T) {
2794+
d := New(0, 0)
2795+
2796+
_, err := d.Ln(5)
2797+
2798+
if err == nil {
2799+
t.Errorf("expected error, natural logarithm of 0 cannot be represented (-infinity)")
2800+
}
2801+
}
2802+
2803+
func TestDecimal_LnNegative(t *testing.T) {
2804+
d := New(-20, 2)
2805+
2806+
_, err := d.Ln(5)
2807+
2808+
if err == nil {
2809+
t.Errorf("expected error, natural logarithm cannot be calculated for nagative decimals")
2810+
}
2811+
}
2812+
27522813
func TestDecimal_NumDigits(t *testing.T) {
27532814
for _, testCase := range []struct {
27542815
Dec string

0 commit comments

Comments
 (0)