@@ -808,6 +808,123 @@ func (d Decimal) ExpTaylor(precision int32) (Decimal, error) {
808
808
return result , nil
809
809
}
810
810
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
+
811
928
// NumDigits returns the number of digits of the decimal coefficient (d.Value)
812
929
// Note: Current implementation is extremely slow for large decimals and/or decimals with large fractional part
813
930
func (d Decimal ) NumDigits () int {
0 commit comments