/src/r-source/src/nmath/fprec.c
Line | Count | Source |
1 | | /* |
2 | | * Mathlib : A C Library of Special Functions |
3 | | * Copyright (C) 2000-2025 The R Core Team |
4 | | * Copyright (C) 1998 Ross Ihaka |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify |
7 | | * it under the terms of the GNU General Public License as published by |
8 | | * the Free Software Foundation; either version 2 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, a copy is available at |
18 | | * https://www.R-project.org/Licenses/ |
19 | | * |
20 | | * SYNOPSIS |
21 | | * |
22 | | * #include <Rmath.h> |
23 | | * double fprec(double x, double digits); |
24 | | * |
25 | | * DESCRIPTION |
26 | | * |
27 | | * Returns the value of x rounded to "digits" significant |
28 | | * decimal digits. |
29 | | * |
30 | | * NOTES |
31 | | * |
32 | | * This routine is a translation into C of a Fortran subroutine |
33 | | * by W. Fullerton of Los Alamos Scientific Laboratory. |
34 | | * Some modifications have been made so that the routines |
35 | | * conform to the IEEE 754 standard. |
36 | | */ |
37 | | |
38 | | #include <config.h> |
39 | | #include "nmath.h" |
40 | | |
41 | | /* Improvements by Martin Maechler, May 1997; |
42 | | further ones, Feb.2000: |
43 | | Replace pow(x, (double)i) by R_pow_di(x, i) {and use int dig} */ |
44 | | |
45 | 3.34k | #define MAX_DIGITS 22 |
46 | | /* was till R 0.99: DBL_DIG := digits of precision of a double, usually 15 */ |
47 | | /* FIXME: Hmm, have quite a host of these: |
48 | | |
49 | | 1) ./fround.c uses much more (sensibly!) ``instead'' |
50 | | 2) ../main/coerce.c & ../main/deparse.c have DBL_DIG directly |
51 | | 3) ../main/options.c has #define MAX_DIGITS 22 for options(digits) |
52 | | |
53 | | Really should decide on a (config.h dependent?) global MAX_DIGITS. |
54 | | --MM-- |
55 | | */ |
56 | | |
57 | | // R's signif(x, digits) via Math2(args, fprec) in ../main/arithmetic.c : |
58 | | double fprec(double x, double digits) |
59 | 4.54k | { |
60 | 4.54k | if (ISNAN(x) || ISNAN(digits)) |
61 | 0 | return x + digits; |
62 | 4.54k | if (!R_FINITE(x)) return x; |
63 | 4.26k | if (!R_FINITE(digits)) { |
64 | 0 | if(digits > 0.) return x; |
65 | 0 | else digits = 1.; |
66 | 0 | } |
67 | 4.26k | if(x == 0) return x; |
68 | | |
69 | 3.06k | int dig = (int)round(digits); |
70 | 3.06k | if (dig > MAX_DIGITS) { |
71 | 0 | return x; |
72 | 3.06k | } else if (dig < 1) |
73 | 0 | dig = 1; |
74 | | |
75 | 3.06k | double sgn = 1.; |
76 | 3.06k | if(x < 0.0) { |
77 | 286 | sgn = -sgn; |
78 | 286 | x = -x; |
79 | 286 | } |
80 | 3.06k | double l10 = log10(x); |
81 | 3.06k | int e10 = dig-1 - (int)floor(l10); |
82 | | // Max.expon. of 10 (w/o denormalizing or overflow; = R's trunc( log10(.Machine$double.xmax) ): |
83 | 3.06k | const static int max10e = (int) DBL_MAX_10_EXP; // == 308 ("IEEE") |
84 | 3.06k | if(fabs(l10) < max10e - 2) { |
85 | 2.77k | double p10 = 1.; |
86 | 2.77k | if(e10 > max10e) { /* numbers less than 10^(dig-1) * 1e-308 */ |
87 | 18 | p10 = R_pow_di(10., e10-max10e); |
88 | 18 | e10 = max10e; |
89 | 18 | } |
90 | 2.77k | double pow10; |
91 | 2.77k | if(e10 > 0) { /* Try always to have pow >= 1 |
92 | | and so exactly representable */ |
93 | 2.17k | pow10 = R_pow_di(10., e10); |
94 | 2.17k | return(sgn*(nearbyint((x*pow10)*p10)/pow10)/p10); |
95 | 2.17k | } else { |
96 | 603 | pow10 = R_pow_di(10., -e10); |
97 | 603 | return(sgn*(nearbyint((x/pow10))*pow10)); |
98 | 603 | } |
99 | 2.77k | } else { /* -- LARGE or small -- */ |
100 | 282 | bool do_round = log10(DBL_MAX) - l10 >= R_pow_di(10., -dig); /* e.g. signif(1.09e308, 2) */ |
101 | 282 | int e2 = dig + ((e10 > 0)? 1 : -1) * MAX_DIGITS; |
102 | 282 | double |
103 | 282 | p10 = R_pow_di(10., e2), |
104 | 282 | P10 = R_pow_di(10., e10-e2); |
105 | 282 | x *= p10; |
106 | 282 | x *= P10; |
107 | | /*-- p10 * P10 = 10 ^ e10 */ |
108 | 282 | if(do_round) x += 0.5; |
109 | 282 | x = floor(x) / p10; |
110 | 282 | return(sgn*x/P10); |
111 | 282 | } |
112 | 3.06k | } |