Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add nearest-value rounding support #342

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export(debug_font_dev)
export(decimal_align)
export(default_hsep)
export(default_page_number)
export(default_rounding)
export(diagnose_pagination)
export(divider_height)
export(do_forced_paginate)
Expand Down Expand Up @@ -99,6 +100,7 @@ export(ref_df_row)
export(round_fmt)
export(set_default_hsep)
export(set_default_page_number)
export(set_default_rounding)
export(spans_to_viscell)
export(split_word_ttype)
export(spread_integer)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## formatters 0.5.10.9001
* Fixed a bug in `mform_handle_newlines` that caused string matrix column names to be removed. This prevented paginated listing key column info from being repeated when vertically spanning multiple pages.
* Fixed handling for `format_value(format = fun())` for cases where a custom function is used.
* Added handling for rounding types. Specifically, `default_rounding()` and `get_default_rounding()` now allow for setting and getting the rounding type for `format_value()`.

## formatters 0.5.10
* Fixed a bug in `mf_update_cinfo` causing an error when `export_as_txt` was applied to empty listings.
Expand Down
57 changes: 48 additions & 9 deletions R/format_value.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
fun_takes <- function(f, nm) {
nm %in% names(formals(f))
}

call_format_fun <- function(f,
value,
na_str,
output) {
args <- c(
list(value),
if (fun_takes(f, "na_str")) list(na_str = na_str),
if (fun_takes(f, "output")) list(output = output)
)
do.call(f, args)
}


formats_1d <- c(
"xx", "xx.", "xx.x", "xx.xx", "xx.xxx", "xx.xxxx",
"xx%", "xx.%", "xx.x%", "xx.xx%", "xx.xxx%", "(N=xx)", "N=xx", ">999.9", ">999.99",
Expand Down Expand Up @@ -146,16 +163,20 @@ sprintf_format <- function(format) {
#' @param na_str (`string`)\cr the value to return if `x` is `NA`.
#'
#' @details
#' This function combines the rounding behavior of R's standards-compliant [round()]
#' function (see the Details section of that documentation) with the strict decimal display
#' of [sprintf()]. The exact behavior is as follows:
#' This function combines rounding behavior with the strict decimal display of
#' [sprintf()]. By default, R's standards-compliant [round()]
#' function (see the Details section of that documentation) is used. The exact
#' behavior is as follows:
#'
#' \enumerate{
#' \item{If `x` is `NA`, the value of `na_str` is returned.}
#' \item{If `x` is non-`NA` but `digits` is `NA`, `x` is converted to a character and returned.}
#' \item{If `x` and `digits` are both non-NA, [round()] is called first, and then [sprintf()]
#' is used to convert the rounded value to a character with the appropriate number of trailing
#' zeros enforced.}
#' \item{If you need to change the type of rounding to perform, please use `set_default_rounding(round_type)`.
#' With `round_type = "iec"`, the default, rounding is compliant with IEC 60559 (see details), while
#' `round_type = "sas"` performs nearest-value rounding consistent with rounding within SAS.}
#' }
#'
#' @return A character value representing the value after rounding, containing any trailing zeros
Expand All @@ -169,9 +190,9 @@ sprintf_format <- function(format) {
#' not at least `digits` significant digits after the decimal that remain after rounding. It *may* differ from
#' `sprintf("\%.Nf", x)` for values ending in `5` after the decimal place on many popular operating systems
#' due to `round`'s stricter adherence to the IEC 60559 standard, particularly for R versions > 4.0.0 (see
#' warning in [round()] documentation).
#' warning in [round()] documentation). For changing the rounding behavior, see `set_default_rounding()`.
#'
#' @seealso [format_value()], [round()], [sprintf()]
#' @seealso [set_default_rounding()], [format_value()], [round()], [sprintf()]
#'
#' @examples
#' round_fmt(0, digits = 3)
Expand All @@ -191,11 +212,28 @@ round_fmt <- function(x, digits, na_str = "NA") {
} else if (is.na(digits)) {
paste0(x)
} else {
rndx <- switch(default_rounding(),
iec = round(x, digits),
sas = round_sas(x, digits)
)
sprfmt <- paste0("%.", digits, "f")
sprintf(fmt = sprfmt, round(x, digits = digits))
sprintf(fmt = sprfmt, rndx)
}
}

## https://stackoverflow.com/questions/12688717/round-up-from-5
round_sas <- function(x, digits = 0) {
# perform SAS rounding
posneg <- sign(x)
z <- abs(x) * 10^digits
z <- z + 0.5 + sqrt(.Machine$double.eps)
z <- trunc(z)
z <- z / 10^digits
z <- z * posneg
## return numeric vector of rounded values
z
}

val_pct_helper <- function(x, dig1, dig2, na_str, pct = TRUE) {
if (pct) {
x[2] <- x[2] * 100
Expand Down Expand Up @@ -272,10 +310,11 @@ format_value <- function(x, format = NULL, output = c("ascii", "html"), na_str =
}
if (length(na_str) == 1) {
if (!all(is.na(x))) {
na_str <- array(na_str, dim = length(x))
## array adds an unneeded dim attribute which causes problems
na_str <- rep(na_str, length(x))
}
} else { # length(na_str) > 1
tmp_na_str <- array("NA", dim = length(x))
tmp_na_str <- rep("NA", length(x))
tmp_na_str[is.na(x)] <- na_str[seq(sum(is.na(x)))]
na_str <- tmp_na_str
}
Expand All @@ -288,7 +327,7 @@ format_value <- function(x, format = NULL, output = c("ascii", "html"), na_str =
} else if (is.null(format)) {
toString(x)
} else if (is.function(format)) {
format(x, output = output)
call_format_fun(f = format, value = x, na_str = na_str, output = output)
} else if (is.character(format)) {
l <- if (format %in% formats_1d) {
1
Expand Down
49 changes: 0 additions & 49 deletions R/tostring.R
Original file line number Diff line number Diff line change
Expand Up @@ -205,55 +205,6 @@ gpar_from_fspec <- function(fontspec) {

font_dev_is_open <- function() font_dev_state$open

#' Default horizontal separator
#'
#' The default horizontal separator character which can be displayed in the current
#' charset for use in rendering table-like objects.
#'
#' @param hsep_char (`string`)\cr character that will be set in the R environment
#' options as the default horizontal separator. Must be a single character. Use
#' `getOption("formatters_default_hsep")` to get its current value (`NULL` if not set).
#'
#' @return unicode 2014 (long dash for generating solid horizontal line) if in a
#' locale that uses a UTF character set, otherwise an ASCII hyphen with a
#' once-per-session warning.
#'
#' @examples
#' default_hsep()
#' set_default_hsep("o")
#' default_hsep()
#'
#' @name default_horizontal_sep
#' @export
default_hsep <- function() {
system_default_hsep <- getOption("formatters_default_hsep")

if (is.null(system_default_hsep)) {
if (any(grepl("^UTF", utils::localeToCharset()))) {
hsep <- "\u2014"
} else {
if (interactive()) {
warning(
"Detected non-UTF charset. Falling back to '-' ",
"as default header/body separator. This warning ",
"will only be shown once per R session."
) # nocov
} # nocov
hsep <- "-" # nocov
}
} else {
hsep <- system_default_hsep
}
hsep
}

#' @name default_horizontal_sep
#' @export
set_default_hsep <- function(hsep_char) {
checkmate::assert_character(hsep_char, n.chars = 1, len = 1, null.ok = TRUE)
options("formatters_default_hsep" = hsep_char)
}

.calc_cell_widths <- function(mat, colwidths, col_gap) {
spans <- mat$spans
keep_mat <- mat$display
Expand Down
37 changes: 37 additions & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,43 @@ set_default_hsep <- function(hsep_char) {
options("formatters_default_hsep" = hsep_char)
}

#' Default rounding type
#'
#' The default rounding type for numeric values in any formatting outputs like [format_value()].
#'
#' @param round_type (`string`)\cr single character value to set the default rounding type. It can be
#' either `"iec"` or `"sas"` for IEC 60559 or SAS rounding (nearest-value rounding), respectively.
#'
#' @return The default rounding type (`"iec"` if not set).
#'
#' @examples
#' default_rounding()
#' set_default_rounding("sas")
#' default_rounding()
#'
#' @name default_rounding
#' @export
default_rounding <- function() {
formatters_default_rounding <- getOption("formatters_default_rounding")

rounding <- if (is.null(formatters_default_rounding)) {
"iec"
} else {
formatters_default_rounding
}
rounding
}

#' @name default_rounding
#' @export
set_default_rounding <- function(round_type = c("iec", "sas")) {
round_type <- round_type[1]
checkmate::assert_character(round_type, len = 1, null.ok = TRUE)
checkmate::assert_choice(round_type, c("iec", "sas"), null.ok = TRUE)
options("formatters_default_rounding" = round_type)
}


#' Default page number format
#'
#' If set, the default page number string will appear on the bottom right of
Expand Down
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ reference:
- ifnotlen0
- table_inset
- default_horizontal_sep
- default_rounding
- mf_strings
- page_lcpp
- page_types
Expand Down
1 change: 0 additions & 1 deletion formatters.Rproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Version: 1.0
ProjectId: 91d83c00-f38c-4245-80dc-783a2cfe8487

RestoreWorkspace: Default
SaveWorkspace: Default
Expand Down
17 changes: 1 addition & 16 deletions man/default_horizontal_sep.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions man/default_rounding.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions man/round_fmt.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions tests/testthat/test-formatters.R
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
values <- c(5.123456, 7.891112)

test_that("Default horizontal separator works", {
expect_true(is.null(getOption("formatters_default_hsep")))
expect_error(set_default_hsep("foo"))
expect_silent(set_default_hsep("a"))
expect_equal(default_hsep(), "a")
expect_silent(set_default_hsep(NULL))
expect_true(default_hsep() %in% c("\u2014", "-"))
})

test_that("Default horizontal separator works", {
expect_true(is.null(getOption("formatters_default_page_number")))
expect_true(is.null(default_page_number()))
Expand Down
Loading
Loading