From 922b5f06e94d19513fa809ca413063b3bb9e7456 Mon Sep 17 00:00:00 2001 From: Leif Andersen Date: Sat, 15 Apr 2017 10:40:41 -0400 Subject: [PATCH] Make log also accept a base. (#1667) Make `log` in `racket/base` optionally accept a second argument. The second argument is the log `base`. The docs also recommend `fllogb` when precision is important. * Error message when base is 1 * Added docs. * Add tests. --- pkgs/racket-doc/info.rkt | 4 ++- .../scribblings/reference/numbers.scrbl | 19 ++++++++--- .../racket-test-core/tests/racket/number.rktl | 10 ++++++ racket/src/racket/src/number.c | 32 +++++++++++++++---- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/pkgs/racket-doc/info.rkt b/pkgs/racket-doc/info.rkt index 5248fe00fa..5f03ca4a75 100644 --- a/pkgs/racket-doc/info.rkt +++ b/pkgs/racket-doc/info.rkt @@ -35,7 +35,9 @@ "planet-doc" "mzscheme-doc" "compiler-lib" - "drracket")) + "drracket" + "math-doc" + "math-lib")) (define pkg-desc "Base Racket documentation") diff --git a/pkgs/racket-doc/scribblings/reference/numbers.scrbl b/pkgs/racket-doc/scribblings/reference/numbers.scrbl index c9881b6acc..29f1584d73 100644 --- a/pkgs/racket-doc/scribblings/reference/numbers.scrbl +++ b/pkgs/racket-doc/scribblings/reference/numbers.scrbl @@ -6,7 +6,8 @@ racket/unsafe/ops racket/require racket/random - racket/list)) + racket/list + math/flonum)) @(define math-eval (make-base-eval)) @examples[#:hidden #:eval math-eval (require racket/math)] @@ -606,19 +607,29 @@ integer.} Returns Euler's number raised to the power of @racket[z]. The result is normally inexact, but it is exact @racket[1] when @racket[z] is an - exact @racket[0]. + exact @racket[0]. See also @racket[expt]. @mz-examples[(exp 1) (exp 2+3i) (exp 0)]} -@defproc[(log [z number?]) number?]{ +@defproc[(log [z number?] [b number? (exp 1)]) number?]{ Returns the natural logarithm of @racket[z]. The result is normally inexact, but it is exact @racket[0] when @racket[z] is an exact @racket[1]. When @racket[z] is exact @racket[0], @exnraise[exn:fail:contract:divide-by-zero]. -@mz-examples[(log (exp 1)) (log 2+3i) (log 1)]} + If @racket[b] is provided, it serves as an alternative + base. It is equivalent to @racket[(/ (log z) (log b))], but + can potentially run faster. If @racket[b] is exact + @racket[1], @exnraise[exn:fail:contract:divide-by-zero]. + + Consider using @racket[fllogb] instead when accuracy is + important. + +@mz-examples[(log (exp 1)) (log 2+3i) (log 1) (log 100 10) (log 8 2) (log 5 5)] + +@history[#:changed "6.9.0.1" @elem{Added second argument for arbitrary bases.}]} @; ------------------------------------------------------------------------ diff --git a/pkgs/racket-test-core/tests/racket/number.rktl b/pkgs/racket-test-core/tests/racket/number.rktl index 64083fdd5b..0ac6727c20 100644 --- a/pkgs/racket-test-core/tests/racket/number.rktl +++ b/pkgs/racket-test-core/tests/racket/number.rktl @@ -2119,7 +2119,11 @@ (test +inf.0 real-part (log -inf.0)) (test +3142.0 round (* 1000 (imag-part (log -inf.0)))) (test +nan.0 log +nan.0) +(test 2.0 log 100 10) +(test 3.0 log 8 2) +(test 1.0 log 5 5) (err/rt-test (log 0) exn:fail:contract:divide-by-zero?) +(err/rt-test (log 5 1) exn:fail:contract:divide-by-zero?) (test 1 cos 0) (test 1.0 cos 0.0) @@ -3386,6 +3390,12 @@ (test -4882.526517254422 real->double-flonum -13737024017780747/2813507303900) (test -9.792844933246106e-14 real->double-flonum -1656/16910305547451097) +;; Arbitrary base log +(test (/ (log 5) (log 20)) log 5 20) +(test (/ (log 5) (log -8)) log 5 -8) +(test (/ (log 7) (log 3+5i)) log 7 3+5i) +(test (/ (log -5+2i) (log 12)) log -5+2i 12) + ;; Hack to use the "math" package when it's available: (when (collection-file-path "base.rkt" "math" #:fail (lambda (x) #f)) (eval diff --git a/racket/src/racket/src/number.c b/racket/src/racket/src/number.c index 222c92e5cf..41516276a0 100644 --- a/racket/src/racket/src/number.c +++ b/racket/src/racket/src/number.c @@ -101,6 +101,7 @@ static Scheme_Object *numerator (int argc, Scheme_Object *argv[]); static Scheme_Object *denominator (int argc, Scheme_Object *argv[]); static Scheme_Object *exp_prim (int argc, Scheme_Object *argv[]); static Scheme_Object *log_prim (int argc, Scheme_Object *argv[]); +static Scheme_Object *log_e_prim (int argc, Scheme_Object *argv[]); static Scheme_Object *sin_prim (int argc, Scheme_Object *argv[]); static Scheme_Object *cos_prim (int argc, Scheme_Object *argv[]); static Scheme_Object *tan_prim (int argc, Scheme_Object *argv[]); @@ -649,12 +650,12 @@ scheme_init_number (Scheme_Env *env) scheme_add_global_constant("exp", scheme_make_folding_prim(exp_prim, "exp", - 1, 1, 1), + 1, 1, 1), env); scheme_add_global_constant("log", - scheme_make_folding_prim(log_prim, + scheme_make_folding_prim(log_prim, "log", - 1, 1, 1), + 1, 2, 1), env); scheme_add_global_constant("sin", scheme_make_folding_prim(sin_prim, @@ -2754,7 +2755,7 @@ static Scheme_Object *un_exp(Scheme_Object *o) static Scheme_Object *un_log(Scheme_Object *o) { - return log_prim(1, &o); + return log_e_prim(1, &o); } static Scheme_Object *numerator(int argc, Scheme_Object *argv[]) @@ -2791,7 +2792,8 @@ static Scheme_Object *complex_log(Scheme_Object *c) m = magnitude(1, &c); theta = angle(1, &c); - return scheme_bin_plus(log_prim(1, &m), scheme_bin_mult(scheme_plus_i, theta)); + return scheme_bin_plus(log_e_prim(1, &m), + scheme_bin_mult(scheme_plus_i, theta)); } static Scheme_Object *bignum_log(Scheme_Object *b) @@ -3027,13 +3029,31 @@ static Scheme_Object *scheme_single_inf_plus_pi() #endif GEN_UNARY_OP(exp_prim, exp, exp, scheme_inf_object, scheme_single_inf_object, scheme_zerod, scheme_zerof, scheme_nan_object, scheme_single_nan_object, complex_exp, GEN_ZERO_IS_ONE, NEVER_RESORT_TO_COMPLEX, BIGNUMS_AS_DOUBLES) -GEN_UNARY_OP(log_prim, log, SCH_LOG, scheme_inf_object, scheme_single_inf_object, scheme_inf_plus_pi(), scheme_single_inf_plus_pi(), scheme_nan_object, scheme_single_nan_object, complex_log, GEN_ONE_IS_ZERO_AND_ZERO_IS_ERR, NEGATIVE_USES_COMPLEX, BIGNUM_LOG) +GEN_UNARY_OP(log_e_prim, log, SCH_LOG, scheme_inf_object, scheme_single_inf_object, scheme_inf_plus_pi(), scheme_single_inf_plus_pi(), scheme_nan_object, scheme_single_nan_object, complex_log, GEN_ONE_IS_ZERO_AND_ZERO_IS_ERR, NEGATIVE_USES_COMPLEX, BIGNUM_LOG) GEN_UNARY_OP(sin_prim, sin, SCH_SIN, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, complex_sin, GEN_ZERO_IS_ZERO, NEVER_RESORT_TO_COMPLEX, BIGNUMS_AS_DOUBLES) GEN_UNARY_OP(cos_prim, cos, SCH_COS, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, complex_cos, GEN_ZERO_IS_ONE, NEVER_RESORT_TO_COMPLEX, BIGNUMS_AS_DOUBLES) GEN_UNARY_OP(tan_prim, tan, SCH_TAN, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, complex_tan, GEN_ZERO_IS_ZERO, NEVER_RESORT_TO_COMPLEX, BIGNUMS_AS_DOUBLES) GEN_UNARY_OP(asin_prim, asin, SCH_ASIN, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, complex_asin, GEN_ZERO_IS_ZERO, OVER_ONE_MAG_USES_COMPLEX, BIGNUMS_AS_DOUBLES) GEN_UNARY_OP(acos_prim, acos, acos, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, scheme_nan_object, scheme_single_nan_object, complex_acos, GEN_ONE_IS_ZERO, OVER_ONE_MAG_USES_COMPLEX, BIGNUMS_AS_DOUBLES) +static Scheme_Object * +log_prim (int argc, Scheme_Object *argv[]) +{ + if (argc == 1) { + return log_e_prim(argc, argv); + } else { + Scheme_Object *a, *b; + a = argv[0]; + b = argv[1]; + if(SAME_OBJ(b, scheme_make_integer(1))){ + scheme_raise_exn(MZEXN_FAIL_CONTRACT_DIVIDE_BY_ZERO, + "log: undefined for base 1"); + ESCAPED_BEFORE_HERE; + } + return scheme_bin_div(un_log(a), un_log(b)); + } +} + static Scheme_Object * atan_prim (int argc, Scheme_Object *argv[]) {