Numbers and Arithmetic

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Checked against R 4.6.1 (2026-06-24) on 2026-10-20; R 4.6.2 is scheduled for 2026-10-29. Everything on this page is base R, so no add-on package version applies; the scripts also use the book class and the data of the running shelf scenario, which are plain base R code, and the lintr 3.4.0 default linters were used only to check the scripts. The CRAN package bit64 is mentioned in prose and was not installed, so nothing here was run with it.

Numbers are the part of R that looks the simplest and hides the most. A bare 1 is not an integer, 0.1 + 0.2 is not 0.3, -7 %/% 2 is -4, (-8)^(1/3) is NaN, and the same double prints as 1e+05 or 100000 depending on a global option. Each of these surprises has the same cause, which is that R keeps numbers in a handful of fixed-size binary formats and then shows you a decimal rendering of them. This page goes through those formats (the 32-bit integer, the 64-bit IEEE 754 double and the complex pair of doubles), what happens at their limits, how to compare and round numbers, how integer division and remainders work, what the bitwise functions and bitstring() show about the stored bits, and how format(), formatC(), prettyNum() and sprintf() turn numbers back into text.

Every example below is a script that was run with Rscript on R 4.6.1 with warnings printed as they happen (options(warn = 1)), and the lines starting with #> are what it printed. The scripts pass the default lintr linters. Errors and warnings that a script shows on purpose are caught with tryCatch() and printed as their conditionMessage(), so the text is exactly what R says. The scripts that use the library scenario of the section (books, loans, shelf_today, loan_days) start with source("scenario.R"), the file described on the section index. The operators themselves are on Operators and Precedence, which also has a section on floating-point comparisons; this page goes into the numbers behind them and does not repeat that section line by line.

The numeric types

The R Language Definition lists the six basic atomic vector types: logical, integer, double (shown as "real"), complex, character and raw. Of these, three hold numbers. The following table puts the numeric ones next to the type tests that people confuse (The R Language Definition: Vectors).

Literal typeof() class() mode() Stored as

1L, 0x10L, 1e3L

"integer"

"integer"

"numeric"

a 32-bit signed integer; NA_integer_ is the bit pattern of the smallest 32-bit value

1, 0.1, 1e3, 0xFF

"double"

"numeric"

"numeric"

a 64-bit IEEE 754 binary floating-point number

1i, 3+4i

"complex"

"complex"

"complex"

a pair of doubles, the real and the imaginary part

TRUE

"logical"

"logical"

"logical"

a 32-bit integer restricted to 0, 1 and NA; arithmetic coerces it to integer

class() says "numeric" for a double and "integer" for an integer, whereas mode() says "numeric" for both; the numeric help page calls it a historical anomaly that floating-point vectors have two names, double and numeric, and typeof() is the name to use when the exact storage matters (R documentation: typeof). is.numeric() is TRUE for integers and doubles (and for factors it is FALSE), and the help page adds that is.complex() and is.numeric() are never both TRUE (R documentation: complex). Most of the time you do not need to care which of the two numeric types a number has: R converts as needed, and An Introduction to R says that for most purposes the user will not be concerned whether the numbers in a vector are integers, reals or complex, because calculations are done as double precision numbers, or double precision complex numbers if the input is complex (An Introduction to R: Vector arithmetic). The exceptions are the subject of the next sections.

Integer versus double

The R Language Definition makes the first point itself: the number returned for the expression 1 is a numeric (a double), and the simplest way to get an integer is the suffix L (The R Language Definition: Constants). The suffix can qualify any number that is an integer in value, so 0x10L is 16 and 1e3L is 1000L. The scripts below check the types of literals and the usual tests, then show which common functions return integers.

Writing integer literals

The same help page that defines integer vectors says why they exist: they let data be passed to C or Fortran code that expects them, and they represent (small) whole numbers exactly and compactly. They are limited to 32 bits, which is about plus or minus two billion, whereas doubles hold much larger whole numbers exactly (R documentation: integer). The literal rules are in the language definition: the suffix L qualifies a decimal, hexadecimal or scientific constant, 1e3L is 1000L (the L qualifies 1e3, not the 3), and a sign is not part of the constant but a unary operator applied to it (The R Language Definition: Literal constants). Hexadecimal constants without the L are doubles: 0xFF is the double 255.

# A bare number is a double; the L suffix makes an integer
typeof(1)
#> [1] "double"
typeof(1L)
#> [1] "integer"
typeof(1e3)
#> [1] "double"
typeof(1e3L)
#> [1] "integer"
typeof(0x10L)
#> [1] "integer"
class(1)
#> [1] "numeric"
class(1L)
#> [1] "integer"
mode(1L)
#> [1] "numeric"
storage.mode(1L)
#> [1] "integer"

If the value after the suffix is not a whole number, or does not fit, R keeps the number and warns while parsing, so the result is a double. The three parse-time warnings below are the real texts; parse() is used to show them because a warning raised by the parser appears before the code runs.

# An integer literal that does not fit becomes a double, with a parse warning
tryCatch(parse(text = "2147483648L"), warning = \(w) conditionMessage(w))
#> [1] "non-integer value 2147483648L qualified with L; using numeric value"
typeof(suppressWarnings(eval(parse(text = "2147483648L"))))
#> [1] "double"
tryCatch(parse(text = "1.5L"), warning = \(w) conditionMessage(w))
#> [1] "integer literal 1.5L contains decimal; using numeric value"
tryCatch(parse(text = "1.L"), warning = \(w) conditionMessage(w))
#> [1] "integer literal 1.L contains unnecessary decimal point"

The list of accepted numeric forms is on the NumericConstants help page, including the C99 hexadecimal floating-point form (0x1.1p1), which is valid R and gives a double (R documentation: NumericConstants).

The is.integer trap and integer-valued doubles

is.integer() tests the storage type, not the value. The help page warns in capital letters that is.integer(x) does not test whether x contains whole numbers, and gives a tolerance-based is.wholenumber() function in its examples (R documentation: integer). The comparison operators convert, so 5 == 5L is TRUE, but identical(5, 5L) is FALSE because the types differ; the same trap hits any identical() test between a value computed with doubles and one read as an integer. A factor and a Date are not numeric for is.numeric(), even though they are stored as an integer and a double.

source("scenario.R")

# is.integer() tests the type, not whether the value is whole
is.integer(5)
#> [1] FALSE
is.integer(5L)
#> [1] TRUE
is.numeric(5L)
#> [1] TRUE
is.double(5L)
#> [1] FALSE
is.numeric(factor(1:2))
#> [1] FALSE
is.numeric(shelf_today)
#> [1] FALSE
5 == 5L
#> [1] TRUE
identical(5, 5L)
#> [1] FALSE

The consequence for code is that you should compare numbers with == or all.equal() and keep identical() for objects whose type you control. A function that must accept "whole numbers" should test the value, as is_whole_number() does in Equality of doubles, rather than the type.

Which functions return integers

Integers come from a small set of places: the L suffix, the colon operator, seq_len() and seq_along(), length(), nchar(), nrow() and ncol(), counting functions such as which() and table(), read.csv() and read.table() columns that contain only whole numbers (the year and pages columns of books), and as.integer(). Arithmetic on integers keeps the type for +, -, *, %% and %/%, and the result of / and ^ is always a double. A single double operand makes the whole result a double. seq(1, 3) is an integer vector because seq.default() without by returns from:to when from is a whole number, while seq(1, 3, by = 1) is a double (R documentation: seq). sum() of integers is an integer when it fits and mean() is always a double.

# Which results are integers, and which whole numbers are still doubles
typeof(1:3)
#> [1] "integer"
typeof(seq_len(3L))
#> [1] "integer"
typeof(seq(1, 3))
#> [1] "integer"
typeof(seq(1, 3, by = 1))
#> [1] "double"
typeof(seq(1L, 3L, by = 1L))
#> [1] "integer"
typeof(length(letters))
#> [1] "integer"
typeof(nchar("shelf"))
#> [1] "integer"
typeof(sum(1:3))
#> [1] "integer"
typeof(sum(c(1, 2, 3)))
#> [1] "double"
typeof(mean(1:3))
#> [1] "double"
typeof(round(2.7))
#> [1] "double"
typeof(abs(-3L))
#> [1] "integer"
typeof(max(1:3))
#> [1] "integer"
typeof(cumsum(1:3))
#> [1] "integer"
typeof(sqrt(4L))
#> [1] "double"
typeof(1:3 * 2L)
#> [1] "integer"
typeof(1:3 * 2)
#> [1] "double"
typeof(TRUE + TRUE)
#> [1] "integer"
typeof(-TRUE)
#> [1] "integer"

The scripts that read the shelf data show the other side of this: the read.csv() columns that hold only whole numbers are integers, a Date is a double that counts days, and the difference of two dates is a difftime whose as.integer() is a plain integer count of days.

source("scenario.R")

# The shelf: stored types of the columns read by read.csv()
typeof(books$year)
#> [1] "integer"
typeof(books$pages)
#> [1] "integer"
typeof(loan_days)
#> [1] "integer"
typeof(loans$loan_date)
#> [1] "double"
class(loans$return_date - loans$loan_date)
#> [1] "difftime"
typeof(as.integer(loans$return_date - loans$loan_date))
#> [1] "integer"

The following table is the type rule of ?Arithmetic for two operands.

Operands + - *, %%, %/% / ^

integer, integer

integer

double

double

integer, double (either order)

double

double

double

double, double

double

double

double

logical, logical or logical, integer

integer

double

double

any operand complex

complex

complex

complex

Integer overflow

Integers are 32-bit, so there is a largest one: .Machine$integer.max, which the .Machine help page says is always 2^31 - 1, that is 2147483647 (R documentation: .Machine). Arithmetic on integers that leaves the range returns NA_integer_ and signals the warning NAs produced by integer overflow. The smallest 32-bit pattern is reserved for NA_integer_, so the usable range is symmetric: minus 2147483647 to plus 2147483647, and -.Machine$integer.max - 1L already overflows. The same sums in doubles are exact, because a double has 53 bits of significand.

# The largest integer, and what happens beyond it
.Machine$integer.max
#> [1] 2147483647
2^31 - 1
#> [1] 2147483647
.Machine$integer.max == 2147483647L
#> [1] TRUE
typeof(.Machine$integer.max)
#> [1] "integer"
# Integer arithmetic past the limit: NA with a warning
tryCatch(.Machine$integer.max + 1L, warning = \(w) conditionMessage(w))
#> [1] "NAs produced by integer overflow"
suppressWarnings(.Machine$integer.max + 1L)
#> [1] NA
tryCatch(-.Machine$integer.max - 1L, warning = \(w) conditionMessage(w))
#> [1] "NAs produced by integer overflow"
tryCatch(100000L * 100000L, warning = \(w) conditionMessage(w))
#> [1] "NAs produced by integer overflow"
.Machine$integer.max + 1
#> [1] 2147483648
100000 * 100000
#> [1] 1e+10
typeof(100000L * 100000)
#> [1] "double"

The warning does not stop the program, so NA flows on through later arithmetic, exactly like any other missing value. A script that must not continue turns warnings into errors (options(warn = 2)) or, better, avoids the situation by choosing the type: use a double for counters, sums of many values, products, timestamps in seconds and identifiers that can pass two billion, and keep integers for indexes, counts and codes that stay small.

sum, cumsum and coercion

Not every integer function overflows. Since R 3.0.2 sum() of integers accumulates in an integer of at least 64 bits, and since R 3.5.0 it returns a double when the total does not fit in an integer instead of an NA with a warning (R NEWS: Changes in R 3.0.2 and 3.5.0). The older warning about integer overflow in sum() that many web pages mention therefore no longer appears. cumsum() has no such rescue: its help page says integer overflow gives NA with a warning, and the message is integer overflow in 'cumsum'; use 'cumsum(as.numeric(.))' (R documentation: cumsum). prod() always returns a double, and cumprod() returns a double for integer input. Coercing a too large double (or a string holding one) with as.integer() returns NA with a different warning, NAs introduced by coercion to integer range, and the same happens for -2147483648, whose pattern is NA_integer_.

# sum() accumulates in 64 bits and returns a double when the total does not fit
big <- c(.Machine$integer.max, 1L)
sum(big)
#> [1] 2147483648
typeof(sum(big))
#> [1] "double"
typeof(sum(1:10))
#> [1] "integer"
mean(big)
#> [1] 1073741824
# cumsum() has no such rescue: it still overflows with a warning
tryCatch(cumsum(big), warning = \(w) conditionMessage(w))
#> [1] "integer overflow in 'cumsum'; use 'cumsum(as.numeric(.))'"
cumsum(as.numeric(big))
#> [1] 2147483647 2147483648
# prod() always returns a double
typeof(prod(1:3))
#> [1] "double"
# Coercing a large double to integer
tryCatch(as.integer(3e9), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion to integer range"
tryCatch(as.integer("3000000000"), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion to integer range"
as.integer(2147483647)
#> [1] 2147483647
as.integer(-2147483647)
#> [1] -2147483647
tryCatch(as.integer(-2147483648), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion to integer range"

Beyond 32-bit integers and 53-bit significands

When 2147483647 is not enough there are three standard answers. The first is to use doubles, which hold every whole number exactly up to 2^53 (about 9.0e15), the point where the spacing between neighbouring doubles becomes 2. The second is the CRAN package bit64, which provides a 64-bit integer class (integer64) stored in a vector of doubles; it is community-maintained, was not installed for this page and nothing here was run with it. The third, for numbers of any size, are the arbitrary-precision packages on CRAN, which are outside this page. Beyond 2^53 whole numbers silently lose their last digits, and the integer-valued factorials show where that begins: the double factorial(22) is still exactly 1124000727777607680000 because the odd part of 22! (its value without the factors of two) fits in 53 bits, and factorial(23) no longer can be.

# Doubles hold whole numbers exactly up to 2^53
2^53
#> [1] 9.007199e+15
2^53 + 1 == 2^53
#> [1] TRUE
print(2^53 + 2, digits = 16)
#> [1] 9007199254740994
factorial(20)
#> [1] 2.432902e+18
print(factorial(20), digits = 19)
#> [1] 2432902008176640000
factorial(22)
#> [1] 1.124001e+21
print(factorial(22), digits = 22)
#> [1] 1124000727777607680000

The shelf has a small example of why the type matters. The loan period is loan_days <- 21L in the scenario; the number of seconds in it is an integer (1814400), and multiplying by 2000 loans overflows the integer type, while the double product is exact.

source("scenario.R")

# Why overflow matters on the shelf: 21 days in seconds, times many loans
seconds <- loan_days * 24L * 60L * 60L
seconds
#> [1] 1814400
tryCatch(seconds * 2000L, warning = \(w) conditionMessage(w))
#> [1] "NAs produced by integer overflow"
seconds * 2000
#> [1] 3628800000

IEEE 754 doubles

The arithmetic of the numeric type is the double precision arithmetic of the IEC 60559 standard, better known as IEEE 754. The Arithmetic help page says that all current R platforms use it, though some details the standard leaves optional, such as the support for denormal numbers, can differ between platforms and even between calculations on one platform (R documentation: Arithmetic). A double has 64 bits:

Field Bits Role

sign

1

0 for positive and 1 for negative; zero has both signs

exponent

11

a power of two, stored with an offset; the all-ones pattern means Inf, -Inf or NaN, the all-zeros pattern means zero or a denormal number

significand

52 stored, 53 effective

the digits of the number in base 2, with the leading 1 implied for normal numbers; .Machine$double.digits is 53

Two consequences follow, and the rest of this section illustrates them. A number is stored exactly only if it is a whole number below 2^53 or a fraction whose denominator is a power of two (1/2, 3/8, 5/1024); every other number, 0.1 included, is rounded to the nearest of the roughly 1.8e19 doubles, and the rounding error is part of the value from then on. And the doubles are not evenly spread: the gap between neighbours is relative to the magnitude, about 2.2e-16 near 1 and 2 near 2^53. The R FAQ says it the same way: the only numbers that can be represented exactly are integers and fractions with a denominator that is a power of 2, and all other numbers are rounded to typically 53 binary digits (R FAQ 7.31: Why doesn’t R think these numbers are equal?).

The stored value of 0.1

bitstring() (new in R 4.6.0, see bitstring and the bit vectors) shows the 64 bits split as sign, exponent and significand, and sprintf() with many digits shows the decimal value that the bits stand for. The pattern of 0.1 is the repeating binary fraction 0.000110011... cut after 52 digits and rounded up, which is why sprintf("%.20f", 0.1) is slightly more than 0.1 while 0.5 is exact.

# A double is 64 bits: 1 sign bit, 11 exponent bits, 52 stored significand bits
bitstring(1)
#> 0 01111111111 0000000000000000000000000000000000000000000000000000
bitstring(0.5)
#> 0 01111111110 0000000000000000000000000000000000000000000000000000
bitstring(0.1)
#> 0 01111111011 1001100110011001100110011001100110011001100110011010
bitstring(-2)
#> 1 10000000000 0000000000000000000000000000000000000000000000000000
format(bitstring(0.1), sep = "|")
#> [1] "0|01111111011|1001100110011001100110011001100110011001100110011010"
# 0.1 is not stored exactly: show the stored value to 20 and to 17 digits
sprintf("%.20f", 0.1)
#> [1] "0.10000000000000000555"
sprintf("%.20f", 0.5)
#> [1] "0.50000000000000000000"
sprintf("%.17g", 0.1)
#> [1] "0.10000000000000001"
sprintf("%.17g", c(0.1 + 0.2, 0.3))
#> [1] "0.30000000000000004" "0.29999999999999999"
print(0.1 + 0.2, digits = 17)
#> [1] 0.30000000000000004
0.1 + 0.2 == 0.3
#> [1] FALSE
0.1 + 0.2 - 0.3
#> [1] 5.551115e-17

Printing hides all of this: print() shows 7 significant digits by default and so prints both 0.1 + 0.2 and 0.3 as 0.3 (see Formatting numbers). The 17 significant digits shown above are enough to tell any two doubles apart.

The .Machine fields

.Machine is a list that describes the numerical characteristics of the platform; the help page says that the integer and double related values are the same for almost all R builds, since all of them use 32-bit integers and IEC 60559 doubles (R documentation: .Machine). The fields that concern doubles and integers are in the table, with the values printed by the next script. The list also holds sizeof.* and longdouble.* entries that depend on the platform and the build (the longdouble.* fields have existed since R 4.0.0 and appear when R uses long doubles), so printing the whole list gives platform-specific output and the script prints only the fields below.

Field Value Meaning

.Machine$integer.max

2147483647

the largest integer, always 2147483647

.Machine$double.base

2

the radix of the representation

.Machine$double.digits

53

base-2 digits in the significand

.Machine$double.eps

2.220446e-16

the gap between 1 and the next larger double

.Machine$double.neg.eps

1.110223e-16

a small positive number x such that 1 - x != 1; half of double.eps because the gap below 1 is half as wide

.Machine$double.xmin

2.225074e-308

the smallest positive normalized double

.Machine$double.xmax

1.797693e+308

the largest finite double

.Machine$double.exponent

11

bits reserved for the exponent

.Machine$double.min.exp

-1022

the most negative power of two of a normalized double

.Machine$double.max.exp

1024

the smallest power of two that overflows

.Machine$double.rounding

5

the rounding action of addition: round to nearest in the IEEE style, with partial underflow

# The limits of the format (the .Machine fields that describe doubles)
.Machine$double.base
#> [1] 2
.Machine$double.digits
#> [1] 53
.Machine$double.eps
#> [1] 2.220446e-16
.Machine$double.neg.eps
#> [1] 1.110223e-16
.Machine$double.xmin
#> [1] 2.225074e-308
.Machine$double.xmax
#> [1] 1.797693e+308
.Machine$double.exponent
#> [1] 11
.Machine$double.min.exp
#> [1] -1022
.Machine$double.max.exp
#> [1] 1024
.Machine$double.rounding
#> [1] 5

Spacing, overflow, underflow and signed zero

The spacing of doubles is what turns small additions into no-ops. The gap above 1 is .Machine$double.eps, so adding half of it changes nothing and adding three quarters of it rounds up to the next double. Near 2^53 the gap is 2, so 2^53 + 1 is the same double as 2^53, and near 1e16 the gap is 2 as well, whereas near 1e15 it is 0.125 and the addition survives.

# eps is the spacing of doubles at 1; the spacing grows with the magnitude
1 + .Machine$double.eps == 1
#> [1] FALSE
1 + .Machine$double.eps / 2 == 1
#> [1] TRUE
1 + .Machine$double.eps * 0.75 == 1
#> [1] FALSE
2^53 + 1 == 2^53
#> [1] TRUE
2^53 + 2 == 2^53
#> [1] FALSE
1e16 + 1 == 1e16
#> [1] TRUE
1e15 + 1 == 1e15
#> [1] FALSE

Past the ends of the range nothing is raised: a result above .Machine$double.xmax becomes Inf, a result below the smallest denormal (about 5e-324) becomes 0, and between 0 and .Machine$double.xmin the doubles lose significand digits gradually, which is called gradual underflow. The help page says the support for denormals is platform dependent, so do not build logic on values below .Machine$double.xmin.

# Beyond the range: overflow to Inf, underflow to 0, denormal numbers
.Machine$double.xmax * 2
#> [1] Inf
.Machine$double.xmax + 1 == .Machine$double.xmax
#> [1] TRUE
10^308
#> [1] 1e+308
10^309
#> [1] Inf
.Machine$double.xmin / 2
#> [1] 1.112537e-308
.Machine$double.xmin / 2 > 0
#> [1] TRUE
5e-324
#> [1] 4.940656e-324
5e-324 / 2
#> [1] 0
2^-1074
#> [1] 4.940656e-324
2^-1075
#> [1] 0

There are also two zeroes, positive and negative, which differ only in the sign bit. == treats them as equal, and so does identical() unless you pass num.eq = FALSE; the difference shows in 1/x, which is Inf or -Inf, and in the bit pattern (R documentation: Arithmetic).

# Two zeroes, and what tells them apart
0 == -0
#> [1] TRUE
identical(0, -0)
#> [1] TRUE
identical(0, -0, num.eq = FALSE)
#> [1] FALSE
1 / 0
#> [1] Inf
1 / -0
#> [1] -Inf
bitstring(0)
#> 0 00000000000 0000000000000000000000000000000000000000000000000000
bitstring(-0)
#> 1 00000000000 0000000000000000000000000000000000000000000000000000

Equality of doubles

The R FAQ entry 7.31 is the canonical answer to the most asked question about R numbers: two floating-point numbers are not reliably equal unless they were computed by the same algorithm, and not always even then. The FAQ example is sqrt(2)^2, which differs from 2 by 4.440892e-16, and the entry points to all.equal() with its default tolerance of .Machine$double.eps ^ 0.5 (R FAQ 7.31: Why doesn’t R think these numbers are equal?). The section on floating-point comparisons of Operators and Precedence reproduces the FAQ examples and the seq() trap; this section adds the three tools you have for the question and their return values.

  • == compares two numbers exactly (the two zeroes are equal, and a comparison with NA or NaN gives NA).

  • identical(x, y) is TRUE only if the two objects are the same in every respect, including type and attributes, and returns a single TRUE or FALSE (R documentation: identical).

  • all.equal(target, current) tests near equality and returns TRUE or a character vector that describes the differences, so it cannot be used directly as a condition: the help page says to wrap it in isTRUE(), or to use identical() where that is appropriate (R documentation: all.equal).

# == compares values, identical() whole objects, all.equal() allows a tolerance
sqrt(2)^2 == 2
#> [1] FALSE
identical(sqrt(2)^2, 2)
#> [1] FALSE
all.equal(sqrt(2)^2, 2)
#> [1] TRUE
isTRUE(all.equal(sqrt(2)^2, 2))
#> [1] TRUE
# all.equal() returns TRUE or a character description: never use it bare in if()
all.equal(1, 1.1)
#> [1] "Mean relative difference: 0.1"
all.equal(1, 1 + 1e-6)
#> [1] "Mean relative difference: 1e-06"
all.equal(1, 1 + 1e-9)
#> [1] TRUE
all.equal(100, 101)
#> [1] "Mean relative difference: 0.01"
all.equal(c(1, 2, 3), c(1, 2, 4))
#> [1] "Mean relative difference: 0.3333333"
all.equal(1L, 1)
#> [1] TRUE
all.equal(1, "1")
#> [1] "Modes: numeric, character"
#> [2] "target is numeric, current is character"
all.equal(c(a = 1), c(b = 1))
#> [1] "Names: 1 string mismatch"
isTRUE(all.equal(1, 1.1))
#> [1] FALSE
tryCatch(if (all.equal(1, 1.1)) "equal", error = \(e) conditionMessage(e))
#> [1] "argument is not interpretable as logical"

The messages are worth knowing: Mean relative difference and Mean absolute difference for numbers, Modes, target is, current is when the types differ (a number against a string), and Names: 1 string mismatch for attributes; check.attributes = FALSE skips the last kind. all.equal(1L, 1) is TRUE, because the tolerance test treats an integer and a double alike, which is another reason identical() is the wrong tool for numbers.

How the tolerance works

The default tolerance is sqrt(.Machine$double.eps), about 1.5e-8. For numeric input, all.equal() takes the mean absolute value of target as the scale; if that scale is finite and above the tolerance, differences are expressed relative to it, and otherwise they are absolute differences (R documentation: all.equal). So all.equal(100, 101) reports a relative difference of 0.01, while all.equal(0, 1e-5) reports an absolute difference of 1e-05 because the target is 0, and all.equal(0, 1e-10) is TRUE since the absolute difference is below the tolerance. A numeric scale forces absolute comparison after dividing by it, and tolerance = 0 makes the report show the difference that the default tolerance hides.

# The tolerance is relative (default: sqrt of the machine epsilon)
.Machine$double.eps^0.5
#> [1] 1.490116e-08
all.equal(1e10, 1e10 + 1)
#> [1] TRUE
all.equal(1e-10, 2e-10)
#> [1] TRUE
all.equal(1e-10, 2e-10, scale = 1)
#> [1] TRUE
all.equal(0, 1e-10)
#> [1] TRUE
all.equal(0, 1e-5)
#> [1] "Mean absolute difference: 1e-05"
all.equal(1, 1.001, tolerance = 0.01)
#> [1] TRUE
all.equal(1, 1.001, tolerance = 1e-4)
#> [1] "Mean relative difference: 0.001"
all.equal(sqrt(2)^2, 2, tolerance = 0)
#> [1] "Mean relative difference: 2.220446e-16"

One fix concerns the top of the range: the NEWS of R 4.1.1 says that all.equal() now returns TRUE correctly when several entries of the vectors are close to .Machine$double.xmax (R NEWS: Changes in R 4.1.1).

Lookups by equality

%in%, match() and unique() compare doubles with exact equality, so they inherit every problem above. The sequence seq(0, 1, by = 0.1) does not contain the double 0.3, because its fourth element is computed by repeated arithmetic; searching for 0.3 fails, searching for the element itself works, and a tolerance, a rounded copy or an integer key fixes it.

# Equality of vectors and the %in% trap
x <- seq(0, 1, by = 0.1)
x[4L] == 0.3
#> [1] FALSE
0.3 %in% x
#> [1] FALSE
x[4L] %in% x
#> [1] TRUE
vapply(x, \(v) isTRUE(all.equal(v, 0.3)), logical(1L))
#>  [1] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
match(0.3, round(x, 10L))
#> [1] 4
which(abs(x - 0.3) < 1e-9)
#> [1] 4

Truncation to integer

as.integer() truncates toward zero, as the integer help page says, so a double that should be whole but is a hair below loses a unit. 0.29 * 100 is 28.999999999999996, which prints as 29 and converts to 28. The remedy is to round before converting, as.integer(round(x)), or to avoid the multiplication by keeping amounts as integers (see The numbers of the shelf). as.integer(2.999999) is 2 because the value is below 3 however close, and as.integer(-2.9) is -2, the same as trunc() and not as floor().

# as.integer() truncates toward zero: just below a whole number loses a unit
as.integer(2.999999)
#> [1] 2
as.integer(-2.9)
#> [1] -2
trunc(-2.9)
#> [1] -2
fee_per_day <- 0.29
fee_per_day * 100
#> [1] 29
print(fee_per_day * 100, digits = 17)
#> [1] 28.999999999999996
as.integer(fee_per_day * 100)
#> [1] 28
as.integer(round(fee_per_day * 100))
#> [1] 29
as.integer(4.35 * 100)
#> [1] 434
as.integer(round(4.35 * 100))
#> [1] 435
# is.integer() tests the type; test for a whole number with a tolerance
is.integer(5)
#> [1] FALSE
x <- c(1, 2.5, 3)
x == round(x)
#> [1]  TRUE FALSE  TRUE
is_whole_number <- function(x, tol = .Machine$double.eps^0.5) {
  abs(x - round(x)) < tol
}
is_whole_number(x)
#> [1]  TRUE FALSE  TRUE

The rules that keep a program safe can be stated briefly:

  • Never test the result of floating-point arithmetic with ==, identical(), %in% or match(); compare with a tolerance (abs(x - y) < tol for vectors, isTRUE(all.equal(x, y)) for one pair of objects).

  • Keep exact quantities (money, counts, identifiers) in integers, in the smallest unit.

  • Round before converting to integer or before building a key or a label from a double.

  • Do not use all.equal() bare in if; it returns a character vector when the objects differ, and if rejects it with argument is not interpretable as logical.

  • Use .Machine$double.eps as the unit of a relative error only near 1; for a number x the gap is about x * .Machine$double.eps.

Rounding

Five functions round, and they are all on one help page: round() to a number of decimal places, signif() to a number of significant digits, and ceiling(), floor() and trunc() to whole numbers in three different directions (R documentation: Round). as.integer() also truncates, and returns an integer instead of a double. The table is the result of the five on the same seven numbers, from the script of Negative numbers and directions; read it by column before you pick a function.

x -2.7 -2.5 -0.2 0 0.2 2.5 2.7 Direction

floor(x)

-3

-3

-1

0

0

2

2

toward minus infinity

ceiling(x)

-2

-2

0

0

1

3

3

toward plus infinity

trunc(x)

-2

-2

0

0

0

2

2

toward zero

round(x)

-3

-2

0

0

0

2

3

to nearest, ties to even

as.integer(x)

-2

-2

0

0

0

2

2

toward zero, type integer

Ties go to the even digit

The help page states the rule for a 5: the IEC 60559 standard is expected to be used, and the result goes to the even digit, so round(0.5) is 0 and round(-1.5) is -2. Half the ties go up and half go down, which removes the systematic upward drift that rounding half up gives when many values ending in 5 are added. The example in the help page carries a comment calling this good behaviour and asking users not to report it as a bug, which is the clearest sign of how often the question is asked. Rounding half up is floor(x + 0.5), and the first chunk shows where it differs.

# Rounding a 5: IEC 60559 "go to the even digit"
x <- seq(-2, 4, by = 0.5)
x
#>  [1] -2.0 -1.5 -1.0 -0.5  0.0  0.5  1.0  1.5  2.0  2.5  3.0  3.5  4.0
round(x)
#>  [1] -2 -2 -1  0  0  0  1  2  2  2  3  4  4
floor(x + 0.5)
#>  [1] -2 -1 -1  0  0  1  1  2  2  3  3  4  4
round(.5 + -2:4)
#> [1] -2  0  0  2  2  4  4
round(2.5)
#> [1] 2
round(3.5)
#> [1] 4
round(-1.5)
#> [1] -2

Rounding to decimals

With digits greater than 0 there is a second effect on top of the tie rule: the argument is a binary double, so the "5" you typed may be stored slightly above or below the real tie. The help page says exactly this, that 0.15 is not represented exactly, the rule applies to the represented number and not to the printed number, and round(0.15, 1) could be either 0.1 or 0.2, depending on the OS services and on representation error. In R 4.0.0 the algorithm for round(x, d) with d > 0 was changed to measure the candidates and round to nearest even, contrary to earlier versions and to rounding based on sprintf() or format() (R documentation: Round); the NEWS entry says that round() now does a better job of rounding to nearest by measuring and to even, and that round(x, dig) for negative dig is much more rational, notably for large dig (R NEWS: Changes in R 4.0.0). The practical result is that the outcome for values close to a tie cannot be predicted from the decimal digits alone; it can be reproduced, but only by running R.

# Rounding to decimals acts on the stored binary value, not the printed one
round(0.15, 1)
#> [1] 0.1
round(0.25, 1)
#> [1] 0.2
round(0.35, 1)
#> [1] 0.3
round(0.45, 1)
#> [1] 0.4
round(2.675, 2)
#> [1] 2.67
print(c(0.15, 0.25, 0.35, 0.45, 2.675), digits = 17)
#> [1] 0.14999999999999999 0.25000000000000000 0.34999999999999998
#> [4] 0.45000000000000001 2.67499999999999982
round(5.55, 1)
#> [1] 5.6
round(1.005, 2)
#> [1] 1

The next chunk is the one to remember when text and numbers have to agree. round() and sprintf("%.1f") do not always give the same digit: 0.05 is stored just above 0.05 and sprintf() rounds the exact binary value up to "0.1", whereas round() returns 0; 5.015 is stored just below the tie, so sprintf() gives "5.01" and round() gives 5.02. The rule for a report is to round once, in one place, with one function, and to format the already rounded value, and the sprintf() and format() ties at exact binary halves go to even (sprintf("%.0f", 2.5) is "2").

# round() and sprintf() can disagree on values close to a tie
x <- c(0.05, 0.45, 5.015)
print(x, digits = 17)
#> [1] 0.050000000000000003 0.450000000000000011 5.014999999999999680
round(x[1:2], 1)
#> [1] 0.0 0.4
sprintf("%.1f", x[1:2])
#> [1] "0.1" "0.5"
round(x[3], 2)
#> [1] 5.02
sprintf("%.2f", x[3])
#> [1] "5.01"
sprintf("%.0f", c(0.5, 1.5, 2.5))
#> [1] "0" "2" "2"
format(2.5, digits = 1)
#> [1] "2"
format(3.5, digits = 1)
#> [1] "4"

Negative digits and significant digits

A negative digits rounds to a power of ten: round(1234.567, -2) is 1200, and the tie rule applies to the tens as well, so round(15, -1) is 20, round(25, -1) is 20 and round(35, -1) is 40. signif(x, digits) is round(x, digits - ceiling(log10(abs(x)))), accepts 1 to 22 digits, and rounds each element on its own, unlike printing, as the help page remarks. Printing a vector chooses one common format, which is why the last line shows 1.2e-03 1.2e+02 1.2e+05: each element is rounded to two significant digits, but the vector is displayed in scientific notation because that is shorter for the set (see Formatting numbers).

# Negative digits round to a power of ten
round(1234.567, -1)
#> [1] 1230
round(1234.567, -2)
#> [1] 1200
round(1234.567, -3)
#> [1] 1000
round(15, -1)
#> [1] 20
round(25, -1)
#> [1] 20
round(35, -1)
#> [1] 40
# signif() counts significant digits
signif(1234.567, 2)
#> [1] 1200
signif(1234.567, 6)
#> [1] 1234.57
signif(0.000123456, 3)
#> [1] 0.000123
signif(123456, 2)
#> [1] 120000
signif(2.5, 1)
#> [1] 2
signif(c(0.00123, 123, 123000), 2)
#> [1] 1.2e-03 1.2e+02 1.2e+05

Negative numbers and directions

floor() goes down and ceiling() goes up, so for negative numbers floor(-2.7) is -3 and ceiling(-2.7) is -2; trunc() and as.integer() drop the fraction, so they go toward zero. They all return doubles except as.integer(); wrap them in as.integer() when an integer is needed, for example for an index or a count, and check the range first, because a double above 2147483647 becomes NA (see sum, cumsum and coercion).

# floor(), ceiling() and trunc() with negative numbers
y <- c(-2.7, -2.5, -0.2, 0, 0.2, 2.5, 2.7)
floor(y)
#> [1] -3 -3 -1  0  0  2  2
ceiling(y)
#> [1] -2 -2  0  0  1  3  3
trunc(y)
#> [1] -2 -2  0  0  0  2  2
round(y)
#> [1] -3 -2  0  0  0  2  3
as.integer(y)
#> [1] -2 -2  0  0  0  2  2
typeof(floor(y))
#> [1] "double"
typeof(as.integer(floor(y)))
#> [1] "integer"

The help page has a warning about floor() and ceiling() that matters for logarithms: the realities of computer arithmetic can cause unexpected results, for example floor(log(x, base = 8)) for x = 8 is mathematically 1 but 0 has been seen on an R platform, and a tolerance is normally necessary. The chunk below gives the correct answers on R 4.6.1 for the three cases it tries, so it proves nothing about other platforms; the safe forms are to round the quotient first (floor(round(x, 9))) or to avoid the logarithm (compare powers, or use integer arithmetic).

# floor() and ceiling() on results that should be whole
floor(log(8, base = 2))
#> [1] 3
floor(log10(1000))
#> [1] 3
log10(1000) == 3
#> [1] TRUE
log(1000, base = 10) == 3
#> [1] TRUE
floor(log(1000, base = 10))
#> [1] 3
floor(round(log(1000, base = 10), 9L))
#> [1] 3

On the shelf the same functions answer practical questions: the mean number of pages (which has two NA values to skip), the page counts rounded to hundreds, and the number of 250-page boxes needed to hold all the pages, which is a ceiling() of a quotient.

source("scenario.R")

# Rounding on the shelf: average pages and page counts to hundreds
mean(books$pages, na.rm = TRUE)
#> [1] 268.2143
round(mean(books$pages, na.rm = TRUE))
#> [1] 268
round(mean(books$pages, na.rm = TRUE), 1L)
#> [1] 268.2
signif(mean(books$pages, na.rm = TRUE), 2L)
#> [1] 270
round(books$pages, -2L)
#>  [1] 400 400 200 200 300  NA 100 300 400 300 100 400 200 300  NA 200
ceiling(books$pages / 100L) * 100L
#>  [1] 400 500 300 200 400  NA 100 300 400 300 100 500 200 400  NA 300
sum(books$pages, na.rm = TRUE)
#> [1] 3755
ceiling(sum(books$pages, na.rm = TRUE) / 250L)
#> [1] 16

Integer division and modulo

R has two operators for division with a whole-number result. x %/% y is floored integer division, floor(x / y), and x %% y is the remainder r <- x %% y, which therefore takes the sign of the divisor y: sign(r) == sign(y). The help page guarantees, up to rounding error, that x == (x %% y) + y * (x %/% y) unless y is zero, and cites Donald Knuth for the floored definition (R documentation: Arithmetic). That definition differs from C, Java, JavaScript and Rust, where % truncates toward zero and the remainder takes the sign of the dividend; Python’s % agrees with R. Code ported from those languages gets different answers for negative operands: -7 %% 2 is 1 in R, not -1.

x, y x %/% y x %% y Check: y * (x %/% y) + (x %% y)

7, 2

3

1

2 * 3 + 1 is 7

-7, 2

-4

1

2 * -4 + 1 is -7

7, -2

-4

-1

-2 * -4 + -1 is 7

-7, -2

3

-1

-2 * 3 + -1 is -7

source("scenario.R")

# Floored division and remainder: the remainder has the sign of the divisor
-7 %/% 2
#> [1] -4
-7 %% 2
#> [1] 1
7 %/% -2
#> [1] -4
7 %% -2
#> [1] -1
-7 %/% -2
#> [1] 3
-7 %% -2
#> [1] -1
x <- c(7, -7, 7, -7)
y <- c(2, 2, -2, -2)
x %/% y
#> [1]  3 -4 -4  3
x %% y
#> [1]  1  1 -1 -1
x == (x %% y) + y * (x %/% y)
#> [1] TRUE TRUE TRUE TRUE
# truncating division, as in C, Java and Rust, with its remainder
trunc(x / y)
#> [1]  3 -3 -3  3
x - y * trunc(x / y)
#> [1]  1 -1  1 -1
sign(x %% y) == sign(y)
#> [1] TRUE TRUE TRUE TRUE

The truncating pair, written with trunc() in the middle of the chunk, is what C gives; to get it, use x - y * trunc(x / y) or sign(x) * (abs(x) %% abs(y)). The floored pair is the useful one for cyclic quantities, since %% of a negative number by a positive one is non-negative: (-3:3) %% 3L cycles through 0, 1, 2, and (0:9 + 3L) %% 7L wraps a counter at 7. It is also what the shelf needs to turn a number of days into weeks and days.

# Types: integer in, integer out; one double makes the result double
typeof(7L %/% 2L)
#> [1] "integer"
typeof(7L %% 2L)
#> [1] "integer"
typeof(7L %/% 2)
#> [1] "double"
typeof(7L / 2L)
#> [1] "double"
7L / 2L
#> [1] 3.5
5.5 %/% 2
#> [1] 2
5.5 %% 2
#> [1] 1.5
-5.5 %% 2
#> [1] 0.5

The type follows the rule of the table in Which functions return integers: integers in, integer out for %/% and %%, one double operand and the result is a double, and / is always a double. Non-integer operands are allowed (5.5 %/% 2 is 2 and 5.5 %% 2 is 1.5), but the help page cautions that the results are subject to representation error: the answer to 1 %/% 0.2 should be 5 and most platforms give 4 because the double nearest to 0.2 is slightly larger than 0.2. The same goes for 0.3 %/% 0.1, which is 2 because 0.3 / 0.1 is stored as 2.9999999999999996.

Division by zero

Dividing by zero is not an error in R. For doubles it follows IEEE 754: a nonzero number divided by zero is Inf or -Inf, and zero divided by zero is NaN. For the integer operators there is no infinite integer, so %/% and %% return NA_integer_, while / always converts to double and gives Inf. The help page says the identity above does not hold when y is zero, where %% is NA_integer_ or NaN depending on the type of the arguments. With infinite operands 3 %% Inf is 3 and -3 %% Inf is Inf (the remainder takes the sign of the divisor), which is the behaviour since R 3.6.2, when x %% L for infinite L stopped returning NaN (R NEWS: Changes in R 3.6.2).

Expression Result Why

5 / 0, -5 / 0

Inf, -Inf

IEEE 754 division by zero

0 / 0, Inf - Inf

NaN

no meaningful value

5 %/% 0, -5 %/% 0

Inf, -Inf

floor of an infinite quotient

5 %% 0

NaN

no remainder exists

5L %/% 0L, 5L %% 0L, 0L %/% 0L

NA

integers have no Inf or NaN

5L / 0L

Inf

/ converts integers to double first

# Division by zero: doubles follow IEEE 754, integers have no Inf
5 %/% 0
#> [1] Inf
-5 %/% 0
#> [1] -Inf
5 %% 0
#> [1] NaN
5L %/% 0L
#> [1] NA
5L %% 0L
#> [1] NA
5 / 0
#> [1] Inf
0 / 0
#> [1] NaN
5L / 0L
#> [1] Inf
0L %/% 0L
#> [1] NA
Inf %% 3
#> [1] NaN
3 %% Inf
#> [1] 3
-3 %% Inf
#> [1] Inf
Inf %/% 3
#> [1] Inf

Accuracy of the remainder

%% on doubles can lose all accuracy if x is much larger than y, and the help page says a warning is given when this is detected. The warning text is probable complete loss of accuracy in modulus. Whether it appears depends on the platform’s floating-point behaviour, so the chunk below uses a deliberately extreme value; for moderately large operands (1e15 %% 7 and 2^53 %% 10) the answers are exact.

# Fractional operands: results are subject to representation error
1 %/% 0.2
#> [1] 4
1 %% 0.2
#> [1] 0.2
0.3 %/% 0.1
#> [1] 2
0.3 / 0.1
#> [1] 3
print(0.3 / 0.1, digits = 17)
#> [1] 2.9999999999999996
# A dividend far larger than the divisor loses all accuracy; R can warn
tryCatch(1e300 %% 3, warning = \(w) conditionMessage(w))
#> [1] "probable complete loss of accuracy in modulus"
1e15 %% 7
#> [1] 6
2^53 %% 10
#> [1] 2

Weeks, days and remainders on the shelf

Loan lengths are the natural integer quantity of the scenario: the number of days between a loan date and a return date is a whole number, kept as an integer by as.integer() of the difftime. %/% 7L and %% 7L split it into weeks and days, pmax(days - loan_days, 0L) gives the days past the 21-day period without ever producing a negative count, and sum(days > loan_days) counts the overdue returned loans (25 of them; the loans still out are added in The numbers of the shelf). The one impossible loan, which was returned three days before it was loaned, shows up as the negative minimum of range(days); arithmetic accepts it, so a check for impossible values has to be written explicitly.

source("scenario.R")

# Shelf: loan lengths in weeks and days, and the days past the 21-day period
returned <- loans[!is.na(loans$loan_date) & !is.na(loans$return_date), ]
days <- as.integer(returned$return_date - returned$loan_date)
head(days, 8L)
#> [1] 21 10  1 26 26 17 24 10
head(days %/% 7L, 8L)
#> [1] 3 1 0 3 3 2 3 1
head(days %% 7L, 8L)
#> [1] 0 3 1 5 5 3 3 3
head(sprintf("%dw %dd", days %/% 7L, days %% 7L), 8L)
#> [1] "3w 0d" "1w 3d" "0w 1d" "3w 5d" "3w 5d" "2w 3d" "3w 3d" "1w 3d"
head(pmax(days - loan_days, 0L), 8L)
#> [1] 0 0 0 5 5 0 3 0
range(days)
#> [1] -3 37
days[days < 0L]
#> [1] -3
sum(days > loan_days)
#> [1] 25
# Checking parity, wrapping around, and a repeating pattern
c(1:6) %% 2L == 0L
#> [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE
(0:9 + 3L) %% 7L
#>  [1] 3 4 5 6 0 1 2 3 4 5
(-3:3) %% 3L
#> [1] 0 1 2 0 1 2 0
seq(0L, 20L, by = 5L) %/% 7L
#> [1] 0 0 1 2 2
# The identity mentioned in ?Arithmetic
q <- days %/% 7L
r <- days %% 7L
all(days == 7L * q + r)
#> [1] TRUE

Complex numbers

A complex number in R is a pair of doubles, the real part and the imaginary part, either of which can be NaN, NA or infinite (R documentation: complex). R has no single literal for a number such as 3+4i: 4i is the imaginary constant, and 3 + 4i is the sum of a double and a complex, evaluated like any other expression (a suffix L after an imaginary constant, 12iL, is a syntax error, as the language definition says). The constructor complex() takes the parts by name, real = and imaginary =, or the polar form, modulus = and argument =, and as.complex() converts numbers and strings.

# Complex literals and constructors
z <- 3 + 4i
z
#> [1] 3+4i
typeof(z)
#> [1] "complex"
class(z)
#> [1] "complex"
is.complex(z)
#> [1] TRUE
is.numeric(z)
#> [1] FALSE
1i^2
#> [1] -1+0i
complex(real = 3, imaginary = 4)
#> [1] 3+4i
complex(modulus = 5, argument = pi / 2)
#> [1] 3.061617e-16+5i
complex(3L)
#> [1] 0+0i 0+0i 0+0i
as.complex(2)
#> [1] 2+0i
as.complex("1i")
#> [1] 0+1i
as.complex("3+4i")
#> [1] 3+4i

The first chunk shows two results that surprise newcomers. 1i^2 is -1+0i and not the real number -1, because the result of a complex power is complex. And complex(modulus = 5, argument = pi / 2) is 3.061617e-16+5i and not 0+5i, because cos(pi / 2) is not exactly zero in floating point. R 4.4.0 changed two things at the edges of this: as.complex("1i") now returns 0+1i instead of NA with a warning, and as.complex(x) for logical, integer and double x keeps the imaginary part at zero even for NA (R NEWS: Changes in R 4.4.0).

Real part, imaginary part, modulus, argument, conjugate

The five basic functions return the parts of the number. For z = x + iy the modulus is r = Mod(z) = sqrt(x^2 + y^2) and the argument is phi = Arg(z), the polar coordinates with x = r * cos(phi) and y = r * sin(phi), and Conj(z) has the sign of the imaginary part flipped (R documentation: complex). abs() of a complex number is its modulus. The product of a number and its conjugate is the squared modulus, a real number with a zero imaginary part.

# The five accessors: real part, imaginary part, modulus, argument, conjugate
Re(z)
#> [1] 3
Im(z)
#> [1] 4
Mod(z)
#> [1] 5
Arg(z)
#> [1] 0.9272952
Conj(z)
#> [1] 3-4i
z * Conj(z)
#> [1] 25+0i
Mod(z)^2
#> [1] 25
sqrt(Re(z)^2 + Im(z)^2) == Mod(z)
#> [1] TRUE

Arithmetic and elementary functions

The arithmetic operators and the elementary functions (exp(), log(), sqrt(), the trigonometric and hyperbolic functions) accept complex arguments, as do %*%, solve(), eigen() and svd() for complex matrices. Addition and subtraction act on the parts separately; multiplication, division and powers follow the usual complex rules, with the principal value for sqrt() and log(). Euler’s identity shows the limit of the representation: exp(1i * pi) is -1+1.224647e-16i, not exactly -1. Before R 4.4.0 the printing of complex vectors rounded the real and imaginary parts together on purpose, which is why older tutorials show -1+0i; since R 4.4.0 format() and print() of complex vectors no longer zap relatively small parts to zero, an API change that makes the rounding error visible (R NEWS: Changes in R 4.4.0). Use round(z, 10) or signif() when you want the clean value.

# Arithmetic: + and - act on each part, * and / follow the complex rules
w <- 1 - 2i
z + w
#> [1] 4+2i
z * w
#> [1] 11-2i
z / w
#> [1] -1+2i
z^2
#> [1] -7+24i
abs(z)
#> [1] 5
exp(1i * pi)
#> [1] -1+1.224647e-16i
Mod(exp(1i * pi))
#> [1] 1
round(exp(1i * pi))
#> [1] -1+0i
sqrt(z)
#> [1] 2+1i
sqrt(z)^2
#> [1] 3+4i

The square root of a negative number

The simplest way to meet complex numbers by accident is sqrt(-1). For a double argument the result is NaN with the warning NaNs produced, and the same holds for log(-1) and asin(2): the real-valued functions never return a complex result unprompted. An Introduction to R makes the same point: sqrt(-17) gives NaN and a warning, but sqrt(-17+0i) does the computations as complex numbers (An Introduction to R: Vector arithmetic). Make the argument complex, with as.complex() or an explicit + 0i, and you get the principal value.

# sqrt(-1) is NaN with a warning, unless the argument is complex
tryCatch(sqrt(-1), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
suppressWarnings(sqrt(-1))
#> [1] NaN
sqrt(as.complex(-1))
#> [1] 0+1i
sqrt(-1 + 0i)
#> [1] 0+1i
tryCatch(log(-1), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
log(as.complex(-1))
#> [1] 0+3.141593i
tryCatch(asin(2), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
asin(as.complex(2))
#> [1] 1.570796-1.316958i

Comparison, sorting and missing values

Complex numbers have no natural order. == and != work, < and > fail with invalid comparison with complex values, and max() and min() fail with invalid 'type' (complex) of argument. sort() and order() do accept complex vectors and order them by real part first and imaginary part second, as the help page of sort says (R documentation: sort); to order by size, order on Mod(). NA_complex_ has both parts NA, and since R 4.4.0 c(NA, 1i) keeps the imaginary part of the first element at zero instead of turning it into NA_complex_ (the chunk shows Im() of that element).

# Comparison: == works, < and max() do not, sort() orders by Re then Im
z == 3 + 4i
#> [1] TRUE
tryCatch(z < w, error = \(e) conditionMessage(e))
#> [1] "invalid comparison with complex values"
tryCatch(max(z, w), error = \(e) conditionMessage(e))
#> [1] "invalid 'type' (complex) of argument"
sort(c(z, w))
#> [1] 1-2i 3+4i
order(Mod(c(z, w)))
#> [1] 2 1
# Rounding and printing of complex numbers
round(exp(1i * pi), 10)
#> [1] -1+0i
print(exp(1i * pi), digits = 3)
#> [1] -1+1.22e-16i
signif(sqrt(2 + 3i), 3)
#> [1] 1.67+0.9i
format(1 / 3 + 2i / 3, digits = 3)
#> [1] "0.333+0.667i"
# A complex number is a pair of doubles, and NA is a whole complex value
is.na(NA_complex_)
#> [1] TRUE
c(1 + 2i, NA)
#> [1] 1+2i   NA
Re(NA_complex_)
#> [1] NA
Im(c(NA, 1i))
#> [1] 0 1

polyroot() finds all complex roots of a polynomial given its coefficients in increasing order, which is the other everyday source of complex numbers: the roots of x^2 + 2x + 5 are -1+2i and -1-2i (R documentation: polyroot). Real data analysis meets complex numbers mostly in the Fourier transform (fft()) and the eigenvalues of non-symmetric matrices: fft() always returns a complex vector, and eigen() returns complex values when the matrix has them.

# Roots of a polynomial: x^2 + 2x + 5 = 0
polyroot(c(5, 2, 1))
#> [1] -1+2i -1-2i
Re(polyroot(c(5, 2, 1)))
#> [1] -1 -1

Powers of negative numbers

R FAQ 7.33, "Why are powers of negative numbers wrong?", is about precedence and not about fractional exponents. The reader who sees -2^2 print -4 is told that the expression is -(2^2), because ^ binds tighter than unary minus, and that the square of -2 is written (-2)^2. The FAQ also shows how to look at the parse tree with as.list(quote(-2^2)) (R FAQ 7.33: Why are powers of negative numbers wrong?). The precedence table is on Operators and Precedence; the first chunk below repeats the FAQ and adds the negative-exponent forms.

# FAQ 7.33: -2^2 is -(2^2) because ^ binds tighter than unary minus
-2^2
#> [1] -4
(-2)^2
#> [1] 4
as.list(quote(-2^2))
#> [[1]]
#> `-`
#>
#> [[2]]
#> 2^2
#>
2^-2
#> [1] 0.25
-2^-2
#> [1] -0.25
(-2)^3
#> [1] -8
(-2)^-3
#> [1] -0.125

The other question people ask, why (-8)^(1/3) is not -2, is answered by the Arithmetic help page and not by the FAQ. For double inputs R uses IEC 60559 arithmetic on all platforms together with the C function pow for ^, and the result of a negative base with a non-integer exponent is mandated by the C99 standard to be NaN (R documentation: Arithmetic). The C standard treats a finite negative base with a finite non-integer exponent as a domain error whose result is NaN, because such a power is in general not a real number. One detail is easy to miss: ^ returns NaN without a warning, whereas sqrt(-4) and log(-4) both warn with NaNs produced. A script that relies on warnings to detect invalid input will therefore miss the power.

# A fractional power of a negative base is NaN, and ^ gives no warning
(-8)^(1 / 3)
#> [1] NaN
withCallingHandlers((-8)^(1 / 3), warning = \(w) stop("warning signalled"))
#> [1] NaN
(-8)^(2 / 3)
#> [1] NaN
(-4)^0.5
#> [1] NaN
(-8)^(3 + 1e-12)
#> [1] NaN
(-8)^2
#> [1] 64
(-8)^3
#> [1] -512
# sqrt() and log() do warn for the same inputs
tryCatch(sqrt(-4), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
tryCatch(log(-4), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"

There are two ways to get an answer. In the complex plane (-8+0i)^(1/3) is the principal cube root, 1+1.732051i, with modulus 2 and argument pi/3 (1.047198); it is one of three cube roots, and not the one that most people want. The real cube root of a negative number, -2, is sign(x) * abs(x)^(1/3), which is correct for odd roots only.

# Two ways to get an answer: go complex, or take the real cube root by hand
as.complex(-8)^(1 / 3)
#> [1] 1+1.732051i
(-8 + 0i)^(1 / 3)
#> [1] 1+1.732051i
Mod((-8 + 0i)^(1 / 3))
#> [1] 2
Arg((-8 + 0i)^(1 / 3))
#> [1] 1.047198
cube_root <- function(x) {
  sign(x) * abs(x)^(1 / 3)
}
cube_root(c(-8, -1, 0, 1, 27))
#> [1] -2 -1  0  1  3
cube_root(-8)^3
#> [1] -8
isTRUE(all.equal(cube_root(-8)^3, -8))
#> [1] TRUE

The corner cases of ^ are specified, so they do not depend on the platform: 1^y and y^0 are 1 always, even for NA and Inf, and x^y gives the proper limit when an argument is infinite. The next chunks list the cases that follow from this; integer arguments do not change the rule that ^ returns a double.

# Corner cases that ?Arithmetic promises: 1^y and y^0 are always 1
1^Inf
#> [1] 1
1^NA
#> [1] 1
NA^0
#> [1] 1
0^0
#> [1] 1
Inf^0
#> [1] 1
0^-1
#> [1] Inf
(-Inf)^3
#> [1] -Inf
(-Inf)^2
#> [1] Inf
2^Inf
#> [1] Inf
2^-Inf
#> [1] 0
0.5^Inf
#> [1] 0
# Integer exponents on negative integers: ^ always returns a double
(-2L)^3L
#> [1] -8
typeof((-2L)^3L)
#> [1] "double"

Inf, NaN and the empty-input values

Floating-point arithmetic never stops with an error: the results of the operations that have no finite answer are the special values of the standard. 1/0 is Inf, -1/0 is -Inf, and 0/0, Inf - Inf, Inf * 0 and Inf / Inf are NaN. Exponentials overflow to Inf or underflow to 0 (exp(710) is Inf, exp(-750) is 0), and log(0) is -Inf. The language definition names Inf and NaN as the results of 1/0 and 0/0 for instance (The R Language Definition: Literal constants). This page keeps to the arithmetic; the semantics of NA and NaN in logic, sorting and data frames are on Missing and Special Values.

# Division by zero is not an error for doubles
1 / 0
#> [1] Inf
-1 / 0
#> [1] -Inf
0 / 0
#> [1] NaN
Inf - Inf
#> [1] NaN
Inf + Inf
#> [1] Inf
Inf * 0
#> [1] NaN
Inf / Inf
#> [1] NaN
Inf * -1
#> [1] -Inf
0 * Inf
#> [1] NaN
5 / Inf
#> [1] 0
log(0)
#> [1] -Inf
exp(710)
#> [1] Inf
exp(-750)
#> [1] 0
exp(Inf)
#> [1] Inf
exp(-Inf)
#> [1] 0
atan(Inf)
#> [1] 1.570796

The tests are is.finite(), is.infinite(), is.nan() and is.na(); the last is TRUE for NaN as well as for NA, whereas is.nan() is TRUE only for NaN. Comparisons with NaN give NA, Inf == Inf is TRUE, and the identical-based tests tell NA_real_ from NaN (R documentation: is.finite).

# Testing for the special values
x <- c(1, Inf, -Inf, NaN, NA)
is.finite(x)
#> [1]  TRUE FALSE FALSE FALSE FALSE
is.infinite(x)
#> [1] FALSE  TRUE  TRUE FALSE FALSE
is.nan(x)
#> [1] FALSE FALSE FALSE  TRUE FALSE
is.na(x)
#> [1] FALSE FALSE FALSE  TRUE  TRUE
Inf == Inf
#> [1] TRUE
NaN == NaN
#> [1] NA
Inf > .Machine$double.xmax
#> [1] TRUE
-Inf < -.Machine$double.xmax
#> [1] TRUE
identical(NaN, NaN)
#> [1] TRUE
identical(NA_real_, NaN)
#> [1] FALSE
x == Inf
#> [1] FALSE  TRUE FALSE    NA    NA

Integers have neither value. 5L / 0L is Inf only because / converts to double first; the integer operators %/% and %% return NA, and an integer overflow gives NA with a warning. The arithmetic functions that have a restricted domain return NaN with the warning NaNs produced, and the summaries of an empty input return the identity of the operation: sum(numeric(0)) is 0, prod(numeric(0)) is 1, mean(numeric(0)) is NaN, and max(numeric(0)) is -Inf with a warning whose text is no non-missing arguments to max; returning -Inf, with min() giving Inf. The help page of NA notes that a computation that involves both NA and NaN may give either, depending on the platform, so do not write code that depends on which one comes out (R documentation: NA).

# Integers have no Inf or NaN: "/" gives a double, other operators give NA
5L / 0L
#> [1] Inf
typeof(5L / 0L)
#> [1] "double"
0L %/% 0L
#> [1] NA
suppressWarnings(.Machine$integer.max + 1L)
#> [1] NA
NA_integer_ + 1L
#> [1] NA
typeof(NA + 1L)
#> [1] "integer"
typeof(NA_real_)
#> [1] "double"
# Functions that return NaN with a warning, and empty input to max() and min()
tryCatch(sqrt(-1), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
tryCatch(log(-1), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
tryCatch(gamma(-1), warning = \(w) conditionMessage(w))
#> [1] "NaNs produced"
tryCatch(max(numeric(0)), warning = \(w) conditionMessage(w))
#> [1] "no non-missing arguments to max; returning -Inf"
tryCatch(min(numeric(0)), warning = \(w) conditionMessage(w))
#> [1] "no non-missing arguments to min; returning Inf"
suppressWarnings(max(numeric(0)))
#> [1] -Inf
suppressWarnings(min(numeric(0)))
#> [1] Inf
sum(numeric(0))
#> [1] 0
prod(numeric(0))
#> [1] 1
mean(numeric(0))
#> [1] NaN
max(-Inf, numeric(0))
#> [1] -Inf
# Summaries propagate the special values
sum(c(1, Inf))
#> [1] Inf
sum(c(1, Inf, -Inf))
#> [1] NaN
mean(c(1, NA))
#> [1] NA
mean(c(1, NA), na.rm = TRUE)
#> [1] 1
range(c(1, Inf, 3))
#> [1]   1 Inf

Bitwise functions and other bases

R has six functions that treat the elements of an integer vector as a set of 32 bits: bitwNot(), bitwAnd(), bitwOr(), bitwXor(), bitwShiftL() and bitwShiftR(), all described on one help page (R documentation: bitwAnd). They were added in R 3.0.0, using the internal interfaces that the "octmode" and "hexmode" classes had used before (R NEWS: Changes in R 3.0.0). The functions are vectorised and recycle their arguments; the result is an integer vector of the length of the longer argument, and NA if an input is NA or the shift is invalid. bitwNot(x) is -x - 1 in two’s complement, so bitwNot(12L) is -13.

# The six bitwise functions on 32-bit integers
bitwAnd(12L, 10L)
#> [1] 8
bitwOr(12L, 10L)
#> [1] 14
bitwXor(12L, 10L)
#> [1] 6
bitwNot(12L)
#> [1] -13
bitwShiftL(1L, 4L)
#> [1] 16
bitwShiftR(256L, 4L)
#> [1] 16
bitwAnd(15L, 7L)
#> [1] 7
bitwXor(-1L, 1L)
#> [1] -2
bitwNot(0:5)
#> [1] -1 -2 -3 -4 -5 -6
bitwAnd(c(1L, 2L, 3L, 4L), 1L)
#> [1] 1 0 1 0

Coercion and shifts

The arguments must be integer vectors; numeric vectors are coerced to integer, which truncates (bitwAnd(12.9, 10) is 8) and warns with NAs introduced by coercion to integer range above 2147483647. The shift count n must be a non-negative integer up to 31. The help page says shifting is done assuming the values represent unsigned integers, so bitwShiftR(-1L, 1L) is 2147483647 (the top bit is cleared) and not -1 as an arithmetic shift would give; and a shift left whose result does not fit in a signed integer is NA: bitwShiftL(1L, 31L) is NA, while bitwShiftL(1L, 30L) is 1073741824.

# Numeric arguments are coerced to integer, which truncates
bitwAnd(12, 10)
#> [1] 8
typeof(bitwAnd(12, 10))
#> [1] "integer"
bitwAnd(12.9, 10)
#> [1] 8
tryCatch(bitwAnd(2^31, 1), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion to integer range"
# Shifts treat the value as unsigned; invalid shifts and overflow give NA
bitwShiftL(1L, 30L)
#> [1] 1073741824
bitwShiftL(1L, 31L)
#> [1] NA
bitwShiftL(1L, 32L)
#> [1] NA
bitwShiftR(-1L, 1L)
#> [1] 2147483647
bitwShiftR(-1, 1:3)
#> [1] 2147483647 1073741823  536870911
bitwShiftL(1L, -1L)
#> [1] NA
bitwAnd(NA_integer_, 1L)
#> [1] NA

For numbers of 2^31 or more, the help page points to the bitops package on CRAN, whose functions work on numeric vectors and treat those values differently; it is community material and was not used here. Within base R the choice is to split the number into two integers (numToInts(), below), or to use the raw type, whose logical operators &, |, xor() and ! already work bitwise (R documentation: bitwAnd).

Bit flags

The classic use of the bitwise functions is a set of yes/no flags in one integer: each flag is a power of two, bitwOr() sets flags, bitwAnd() tests them and bitwAnd(x, bitwNot(flag)) clears one. The shelf has no such field, so the chunk invents the smallest example, three rights of a library member; for anything larger, a named logical vector or a data frame column per flag is clearer and does not have a 31-flag limit.

# Pattern, then check: bit flags kept in an integer
can_read <- 1L
can_borrow <- 2L
can_reserve <- 4L
member_flags <- bitwOr(can_read, can_borrow)
member_flags
#> [1] 3
bitwAnd(member_flags, can_borrow) != 0L
#> [1] TRUE
bitwAnd(member_flags, can_reserve) != 0L
#> [1] FALSE
member_flags <- bitwOr(member_flags, can_reserve)
member_flags
#> [1] 7
member_flags <- bitwAnd(member_flags, bitwNot(can_borrow))
member_flags
#> [1] 5
# The logical operators work bitwise on raw vectors
r15 <- as.raw(15)
r7 <- as.raw(7)
r15 & r7
#> [1] 07
r15 | r7
#> [1] 0f
xor(r15, r7)
#> [1] 08
!r15
#> [1] f0
rawShift(as.raw(1), 3L)
#> [1] 08

Reading and writing other bases

strtoi(x, base = 10L) converts strings to integers in a given base from 2 to 36, using the C function strtol. With base = 0L it chooses the base from the string as C does: a leading 0x is hexadecimal, a leading 0 is octal, anything else decimal. A string that cannot be converted, or does not fit in an integer, gives NA_integer_; strtoi("", base) gives NA on all platforms since R 3.6.0 (R documentation: strtoi). To write a number in another base, sprintf() has %x, %X and %o; and the classes "hexmode" and "octmode" keep an integer and print it in hexadecimal or octal, with the logical operators defined bitwise. format() of a hexmode pads with zeros to a common width, whereas as.character() does not, which is a frequent surprise.

Numeric literals can be written in hexadecimal too: 0xFF is the double 255, 0xFFL the integer, and the hexadecimal floating-point form with a binary exponent 0x1.1p1 is valid and equals 2.125 (R documentation: NumericConstants).

# Other bases: strtoi() reads, format() and sprintf() write
strtoi("ff", base = 16L)
#> [1] 255
strtoi("FF", base = 16L)
#> [1] 255
strtoi("777", base = 8L)
#> [1] 511
strtoi("1010", base = 2L)
#> [1] 10
strtoi("zz", base = 36L)
#> [1] 1295
strtoi(c("0xff", "077", "123"))
#> [1] 255  63 123
strtoi("12abc", base = 10L)
#> [1] NA
strtoi("80000000", base = 16L)
#> [1] NA
strtoi("7fffffff", base = 16L)
#> [1] 2147483647
strtoi("", base = 10L)
#> [1] NA
0xFF
#> [1] 255
0xFFL
#> [1] 255
0x7FFFFFFF
#> [1] 2147483647
typeof(0xFF)
#> [1] "double"
0x1.1p1
#> [1] 2.125
sprintf("%x", 255L)
#> [1] "ff"
sprintf("%X", 255L)
#> [1] "FF"
sprintf("%o", 8L)
#> [1] "10"
sprintf("%08.3f", 3.14159)
#> [1] "0003.142"
sprintf("%5d|%-5d|%05d", 42L, 42L, 42L)
#> [1] "   42|42   |00042"
# hexmode and octmode keep an integer and print it in another base
h <- as.hexmode(255L)
h
#> [1] "ff"
class(h)
#> [1] "hexmode"
as.integer(h)
#> [1] 255
format(h, width = 4L)
#> [1] "00ff"
format(h, upper.case = TRUE)
#> [1] "FF"
as.hexmode(c(10L, 255L, 4096L))
#> [1] "000a" "00ff" "1000"
format(as.hexmode(c(10L, 255L, 4096L)))
#> [1] "000a" "00ff" "1000"
as.character(as.hexmode(c(10L, 255L, 4096L)))
#> [1] "a"    "ff"   "1000"
as.hexmode("1f")
#> [1] "1f"
as.octmode(493L)
#> [1] "755"
as.octmode("755")
#> [1] "755"
as.integer(as.octmode("755"))
#> [1] 493
as.hexmode(12L) & as.hexmode(10L)
#> [1] "8"
as.hexmode(12L) | as.hexmode(10L)
#> [1] "e"

as.octmode() is the class that file permissions use: as.octmode("755") is the integer 493 shown in octal, which is how the mode column of file.info() and the mode argument of Sys.chmod() express a Unix permission (R documentation: octmode). The connection of these classes with the bitwise functions is documented in the help page of bitwAnd: their logical operators are implemented with them.

bitstring and the bit vectors

Seeing the bits of a number is the quickest way to understand integer limits, two’s complement, the layout of a double and the special values. R 4.6.0 added a function for it; the NEWS entry reads "New function bitstring() as a user-friendly wrapper around intToBits(), etc." (R NEWS: Changes in R 4.6.0). bitstring() lives in the utils package, which is attached by default, and its help page says it is inspired by the Julia function of the same name. It supports integer, numeric (double) and raw vectors; for anything else the default method signals Unsupported object. The value is an object of class "bitstring" that stores the bit patterns as character strings together with the type information, and the print() and format() methods insert breakpoints, controlled by sep, between the sign bit, the exponent and the significand, as appropriate (R documentation: bitstring).

# bitstring() (R 4.6.0): the bit pattern of integer, double and raw vectors
bitstring(5L)
#> 0 0000000000000000000000000000101
bitstring(-1L)
#> 1 1111111111111111111111111111111
bitstring(.Machine$integer.max)
#> 0 1111111111111111111111111111111
bitstring(NA_integer_)
#> 1 0000000000000000000000000000000
bitstring(c(1L, 2L, 255L))
#> 0 0000000000000000000000000000001
#> 0 0000000000000000000000000000010
#> 0 0000000000000000000000011111111
bitstring(as.raw(c(1, 15, 255)))
#> 00000001
#> 00001111
#> 11111111
bitstring(charToRaw("bits"))
#> 01100010
#> 01101001
#> 01110100
#> 01110011

An integer is shown as the sign bit followed by 31 bits, -1L is all ones, and .Machine$integer.max is a zero followed by 31 ones. NA_integer_ is a one followed by 31 zeros: the smallest 32-bit value, which R Data Import/Export confirms is the missing value for integer and logical types (R Data Import/Export: Special values). A raw byte is eight bits and is shown without a breakpoint. The charToRaw("bits") line shows the four bytes of the string, 0x62 0x69 0x74 0x73.

A double is shown as 1, 11 and 52 bits. The same manual says that on IEC 60559 platforms NA is represented by the NaN value with the low word 0x7a2, which is 1954 in decimal; the last line of the next chunk, and the R Data Import/Export section on special values, show that payload (the final eleven bits 11110100010 are 1954), while NaN has only the top significand bit set, and Inf and -Inf have an all-ones exponent and a zero significand. That is the whole difference between NA_real_ and NaN, and the reason identical(NA_real_, NaN) is FALSE.

# Doubles: sign, exponent and significand; the special values are patterns
bitstring(1)
#> 0 01111111111 0000000000000000000000000000000000000000000000000000
bitstring(c(Inf, -Inf, NaN, NA_real_))
#> 0 11111111111 0000000000000000000000000000000000000000000000000000
#> 1 11111111111 0000000000000000000000000000000000000000000000000000
#> 0 11111111111 1000000000000000000000000000000000000000000000000000
#> 0 11111111111 0000000000000000000000000000000000000000011110100010

The object is a character vector with a class: as.character() gives the digits without separators, format(x, sep = "") and print(x, sep = "-") change the separator, and nchar() of the character form is 64 for a double and 32 for an integer (the chunk shows the doubles).

# A "bitstring" is a character vector with format and print methods
x <- bitstring(c(0.1, 0.5))
class(x)
#> [1] "bitstring"
as.character(x)
#> [1] "0011111110111001100110011001100110011001100110011001100110011010"
#> [2] "0011111111100000000000000000000000000000000000000000000000000000"
print(x, sep = "-")
#> 0-01111111011-1001100110011001100110011001100110011001100110011010
#> 0-01111111110-0000000000000000000000000000000000000000000000000000
format(x, sep = "")
#> [1] "0011111110111001100110011001100110011001100110011001100110011010"
#> [2] "0011111111100000000000000000000000000000000000000000000000000000"
nchar(as.character(x))
#> [1] 64 64
tryCatch(bitstring("a"), error = \(e) conditionMessage(e))
#> [1] "Unsupported object"
tryCatch(bitstring(TRUE), error = \(e) conditionMessage(e))
#> [1] "Unsupported object"

Two’s complement, the way computers store negative integers, is visible in the next chunk: -5L is the bits of 5 flipped and then increased by one, and bitwNot(5L) + 1L computes the same thing with the bitwise function, which is the identity -x == bitwNot(x) + 1L.

# Two's complement: negating flips the bits and adds one
bitstring(-5L)
#> 1 1111111111111111111111111111011
bitstring(bitwNot(5L))
#> 1 1111111111111111111111111111010
bitstring(bitwNot(5L) + 1L)
#> 1 1111111111111111111111111111011
identical(bitwNot(5L) + 1L, -5L)
#> [1] TRUE

intToBits, packBits and numToBits

bitstring() is a wrapper; the building blocks are in the rawConversion help page. intToBits() returns a raw vector with 32 elements per integer, each 00 or 01, with the least significant bit first, and rawToBits() does the same for the 8 bits of a raw byte. packBits() goes the other way, with type = "raw", "integer" or "double"; it accepts raw, integer or logical input without NA. Reversing the vector and pasting it gives the string that bitstring() shows without the break. For doubles, numToBits() returns the 64 bits and numToInts() the two 32-bit integers, least significant element first; both were added in R 4.1.0, together with the inverse packBits(., type = "double") (R NEWS: Changes in R 4.1.0; R documentation: rawConversion).

# intToBits() gives 32 raw 0/1 values, least significant bit first
bits <- intToBits(5L)
length(bits)
#> [1] 32
class(bits)
#> [1] "raw"
bits[1:8]
#> [1] 01 00 01 00 00 00 00 00
as.integer(bits[1:8])
#> [1] 1 0 1 0 0 0 0 0
paste(rev(as.integer(bits)), collapse = "")
#> [1] "00000000000000000000000000000101"
packBits(bits, type = "integer")
#> [1] 5
packBits(intToBits(c(5L, 6L)), type = "integer")
#> [1] 5 6
packBits(intToBits(-7L), type = "integer")
#> [1] -7
rawToBits(as.raw(5))
#> [1] 01 00 01 00 00 00 00 00
packBits(rawToBits(as.raw(5)))
#> [1] 05
packBits(c(TRUE, FALSE, TRUE, rep(FALSE, 29L)), type = "integer")
#> [1] 5
# Doubles: numToBits() gives 64 bits, numToInts() two integers
length(numToBits(1))
#> [1] 64
numToInts(1)
#> [1]          0 1072693248
numToInts(0.1)
#> [1] -1717986918  1069128089
packBits(numToBits(0.1), type = "double")
#> [1] 0.1
packBits(numToBits(0.1), type = "double") == 0.1
#> [1] TRUE

The same functions give a population count, the number of set bits in a number: the page count 387 of the first book in the scenario is 110000011 in binary, which has four ones. R has no built-in popcount, so a one-line function over intToBits() is the usual answer.

# A bitstring of a count of pages, and a population count of its set bits
pages <- 387L
bitstring(pages)
#> 0 0000000000000000000000110000011
sum(as.integer(intToBits(pages)))
#> [1] 4
popcount <- function(x) {
  vapply(x, \(v) sum(as.integer(intToBits(v))), integer(1L))
}
popcount(c(0L, 1L, 255L, 387L))
#> [1] 0 1 8 4

Formatting numbers

A number has two lives: the binary value in memory and the text that shows it. Four functions turn one into the other, with different jobs. print() and auto-printing show a value in the console. format() returns a character vector in a common format, so that a column lines up. formatC() formats each number on its own with a C-style specification. sprintf() builds a string from a format that mixes text and numbers, and prettyNum() adds marks to something that is already formatted. All of them can only show what the value contains, and none changes it: formatting is the last step, after the rounding.

Printing and the digits option

Console output uses options(digits), which controls the number of significant digits to print and is a suggestion only: valid values are 1 to 22 with default 7 (R documentation: options). print(x, digits = n) sets it for one call, and format() has the same digits argument defaulting to the option. The number of decimals is then chosen so that the smallest element has that many significant digits, which is why format(c(0.001, 0.1), digits = 1) gives "0.001" "0.100": one significant digit for the small number forces three decimals on both. The print.default page adds that for digits of 16 or more the number of significant digits depends on the C library’s sprintf(), so do not rely on the digits beyond 15 being the same across platforms (R documentation: print.default).

# print() shows 7 significant digits by default; format() returns strings
pi
#> [1] 3.141593
print(pi, digits = 3)
#> [1] 3.14
print(pi, digits = 15)
#> [1] 3.14159265358979
format(pi)
#> [1] "3.141593"
format(pi, digits = 3)
#> [1] "3.14"
format(c(1, 10, 100))
#> [1] "  1" " 10" "100"
format(c(0.001, 0.1), digits = 1)
#> [1] "0.001" "0.100"
format(123456789)
#> [1] "123456789"
format(1234567890123)
#> [1] "1.234568e+12"
format(0.1 + 0.2, digits = 17)
#> [1] "0.30000000000000004"
getOption("digits")
#> [1] 7
getOption("scipen")
#> [1] 0

format()

format() takes nsmall (the minimum number of digits to the right of the decimal point in non-scientific formats, 0 to 20), big.mark and decimal.mark, width (the minimum field width), justify for text, scientific (a logical or an integer penalty), and zero.print and drop0trailing for tidying; the last group is passed to prettyNum() (R documentation: format). Two details are worth knowing. nsmall wins over digits: format(3.14159, digits = 3, nsmall = 4) is "3.1416". And big.mark is applied to the string that format() chose, so it cannot turn a scientific string into a grouped one: format(1e6, big.mark = ",") is "1e+06", not "1,000,000", because the scientific form is shorter. The fix is scientific = FALSE. Grouping with a . while the decimal mark is also . warns with 'big.mark' and 'decimal.mark' are both '.', which could be confusing; set decimal.mark = "," as well.

# format(): nsmall and big.mark
format(2, nsmall = 2)
#> [1] "2.00"
format(3.14159, nsmall = 2)
#> [1] "3.14159"
format(3.14159, digits = 3, nsmall = 4)
#> [1] "3.1416"
format(1e6, big.mark = ",")
#> [1] "1e+06"
format(1234567.891, big.mark = ",")
#> [1] "1,234,568"
format(1234567.891, big.mark = ",", nsmall = 2)
#> [1] "1,234,567.89"
tryCatch(format(1234567L, big.mark = "."), warning = \(w) conditionMessage(w))
#> [1] "'big.mark' and 'decimal.mark' are both '.', which could be confusing"
format(1234567L, big.mark = ".", decimal.mark = ",")
#> [1] "1.234.567"
# format(): width, justify, NA and the special values
format(42L, width = 8L)
#> [1] "      42"
format(c("a", "bb", "ccc"), width = 5L)
#> [1] "a    " "bb   " "ccc  "
format(c("a", "bb", "ccc"), justify = "right")
#> [1] "  a" " bb" "ccc"
format(-1.5)
#> [1] "-1.5"
format(c(-1, 1))
#> [1] "-1" " 1"
format(TRUE)
#> [1] "TRUE"
format(NA)
#> [1] "NA"
format(c(1.5, NA, 3))
#> [1] "1.5" " NA" "3.0"
format(NaN)
#> [1] "NaN"
format(Inf)
#> [1] "Inf"
format(1e-20)
#> [1] "1e-20"

Vectors are formatted to a common width and a common number of decimals (format(c(1, 10, 100)) pads to three characters, and format(c(1.5, NA, 3)) gives "1.5" " NA" "3.0"), while trim = TRUE removes the padding. NA, NaN and Inf are encoded as "NA", "NaN" and "Inf". The choice between fixed and scientific notation is the next subsection; scientific = FALSE forces fixed, and since R 4.0.0 it corresponds to the most extreme scipen setting instead of the arbitrary 100 that earlier versions used (R NEWS: Changes in R 4.0.0).

# format(): fixed or scientific notation
format(1e5)
#> [1] "1e+05"
format(1e5, scientific = FALSE)
#> [1] "100000"
format(123456, scientific = TRUE)
#> [1] "1.23456e+05"
format(0.00001234, scientific = FALSE)
#> [1] "0.00001234"
format(1e-15, scientific = FALSE)
#> [1] "0.000000000000001"
format(1e15, scientific = FALSE)
#> [1] "1000000000000000"
format(1e22, scientific = FALSE)
#> [1] "10000000000000000000000"
format(2^60, scientific = FALSE)
#> [1] "1152921504606846976"
format(2^60, scientific = FALSE, big.mark = ",")
#> [1] "1,152,921,504,606,846,976"

formatC()

formatC(x, digits, width, format, flag, mode, big.mark) follows the C printf family. The format argument is "d" for integers, "f" for fixed notation (digits is the number of decimals), "e" and "E" for scientific, "g" and "G" for the shorter of the two with trailing zeros dropped (digits is the number of significant digits), "fg" for fixed notation where digits is the minimum number of significant digits, and "s" for strings. The flag characters are "0" (pad with zeros), "-" (left-justify), "+" (always show the sign), " " (a space for positive numbers) and "#" (an alternative form); several may be combined, and a negative width is the same as the "-" flag. Unlike format(), it formats the elements individually and does not align decimals across a vector (R documentation: formatC).

# formatC(): C-style formats, with digits, width, flag and big.mark
formatC(3.14159, format = "f", digits = 2)
#> [1] "3.14"
formatC(3.14159, format = "e", digits = 2)
#> [1] "3.14e+00"
formatC(3.14159, format = "g", digits = 3)
#> [1] "3.14"
formatC(1234567, format = "d", big.mark = ",")
#> [1] "1,234,567"
# formatC(): width and flag
formatC(42L, width = 8L, flag = "0")
#> [1] "00000042"
formatC(42L, width = 8L, flag = "-")
#> [1] "42      "
formatC(42L, width = 8L, flag = " ")
#> [1] "      42"
formatC(c(1, 10, 100), width = 5L, format = "d", flag = "0")
#> [1] "00001" "00010" "00100"
formatC(3.14159, width = 10L, format = "f", digits = 2, flag = "+")
#> [1] "     +3.14"
formatC(0.000123, format = "g")
#> [1] "0.000123"
formatC(123456789, format = "g")
#> [1] "1.235e+08"
formatC(123456789, format = "fg", digits = 3)
#> [1] "123456789"
formatC(123456789, digits = 9L, format = "fg", big.mark = ",")
#> [1] "123,456,789"
formatC(c(1.5, 10.25, 100), format = "f", digits = 1, width = 7L)
#> [1] "    1.5" "   10.2" "  100.0"

Two behaviours are easy to miss. formatC() of a double uses "g" unless you ask for another format, with a few significant digits, so formatC(123456789, format = "g") is "1.235e+08" rather than the 9 digits that the number has; always give format (and digits) when the output is for people. And formatC(NA) fails because the logical NA has an unsupported type: NA_real_ works (R documentation: formatC).

# formatC(): strings, names, errors and missing values
formatC("abc", width = 6L)
#> [1] "   abc"
formatC("abc", width = -6L)
#> [1] "abc   "
formatC(c(a = 1.5, b = 2.25), format = "f", digits = 1)
#>     a     b
#> "1.5" "2.2"
tryCatch(formatC(0.5, format = "%"), error = \(e) conditionMessage(e))
#> [1] "'format' must be one of {\"f\",\"e\",\"E\",\"g\",\"G\", \"fg\", \"s\"}"
formatC(1e6, format = "d", big.mark = ",")
#> [1] "1,000,000"
formatC(1e6, format = "f", digits = 0, big.mark = ",")
#> [1] "1,000,000"
formatC(character(0))
#> character(0)
tryCatch(formatC(NA), error = \(e) conditionMessage(e))
#> [1] "unsupported type "
formatC(NA_real_)
#> [1] "NA"
formatC(NaN)
#> [1] "NaN"
formatC(c(1, NA, Inf), format = "f", digits = 1)
#> [1] "1.0" " NA" "Inf"

prettyNum()

prettyNum() inserts marks into numbers that are already numbers or strings: big.mark between every big.interval digits before the decimal point, small.mark after it, decimal.mark in place of the point, zero.print to replace zeros (for example by a dash in a table), and drop0trailing. format() and formatC() call it for their own marks, and called directly on a number it first converts the number with format(), so it shares the 7-digit limit: prettyNum(1234567.891, big.mark = ",") is "1,234,568" (R documentation: prettyNum). Pass a string, or format the number first, to control the digits.

# prettyNum(): insert big marks into numbers or strings, and tidy zeros
prettyNum(1234567, big.mark = ",")
#> [1] "1,234,567"
prettyNum(123456789L, big.mark = ",")
#> [1] "123,456,789"
prettyNum(c(123456, 1234567), big.mark = " ")
#> [1] "123 456"   "1 234 567"
prettyNum(1234567.891, big.mark = ",")
#> [1] "1,234,568"
prettyNum("1234567", big.mark = ",")
#> [1] "1,234,567"
prettyNum(c(0, 1.5, 0), zero.print = "-")
#> [1] "-"   "1.5" "-"

sprintf()

sprintf(fmt, ...) is the vectorised C sprintf, with %d for integers, %f, %e and %g for doubles, %s for strings and %% for a percent sign, flags such as -, +, 0 and a space, and a width and precision (%8.2f). A * takes the width from an argument, and %2$s selects an argument by position. %d accepts a double only if it is a whole number (sprintf("%d", 3) works, sprintf("%d", 3.5) is an error whose text is invalid format '%d'; use format %f, %e, %g or %a for numeric objects). %s converts a number with as.character(), which keeps up to 15 significant digits. There is no thousands flag: %,d is an error. And sprintf() does not use getOption("OutDec"), so its decimal separator is always the point, unlike format() and formatC() (R documentation: sprintf).

# sprintf(): vectorised C-style formatting
sprintf("%.2f", 3.14159)
#> [1] "3.14"
sprintf("%8.2f", 3.14159)
#> [1] "    3.14"
sprintf("%-8.2f|", 3.14159)
#> [1] "3.14    |"
sprintf("%+d", 5L)
#> [1] "+5"
sprintf("%e", 12345.6789)
#> [1] "1.234568e+04"
sprintf("%g", c(123456, 1234567, 0.0001234, 0.00001234))
#> [1] "123456"      "1.23457e+06" "0.0001234"   "1.234e-05"
sprintf("%5.1f%%", 12.345)
#> [1] " 12.3%"
sprintf("%s has %d pages", "Ten Winters", 322L)
#> [1] "Ten Winters has 322 pages"
sprintf("%d", 3)
#> [1] "3"
tryCatch(sprintf("%d", 3.5), error = \(e) conditionMessage(e))
#> [1] "invalid format '%d'; use format %f, %e, %g or %a for numeric objects"
sprintf("%s", 3.5)
#> [1] "3.5"
sprintf("%s", 1e6)
#> [1] "1e+06"
sprintf("%s", 123456.7)
#> [1] "123456.7"
sprintf("%.0f", 1e15)
#> [1] "1000000000000000"
tryCatch(sprintf("%,d", 1000L), error = \(e) conditionMessage(e))
#> [1] "invalid format '%,d'; use format %d, %i, %o, %x or %X for integer objects"
sprintf("%2$s %1$s", "world", "hello")
#> [1] "hello world"
sprintf("%*d", 6L, 42L)
#> [1] "    42"
sprintf("%.3s", "abcdef")
#> [1] "abc"

Fixed or scientific: options(scipen)

When R prints a double it computes the width of the fixed and of the scientific representation and uses fixed notation unless it is wider than the scientific one by more than scipen characters. The default is 0: positive values bias toward fixed notation and negative values toward scientific notation. The help page says valid values are between -9 and 9999 with relevant values for double precision in -6 to 319, and that invalid values warn and are set to a boundary (R documentation: options). Since R 4.5.0 options(scipen = NULL) and other invalid values signal an error instead of invalidating the operations that rely on a finite integer (R NEWS: Changes in R 4.5.0); the chunk shows the real text. The scientific form of 1e5 is shorter than 100000, so by default 1e5 prints as 1e+05 and 123456 prints in full; a large scipen removes the exponent from every number, and a negative one adds it to short ones.

# options(scipen): the penalty that favours fixed over scientific notation
op <- options(scipen = 0)
1e5
#> [1] 1e+05
123456
#> [1] 123456
1e15
#> [1] 1e+15
0.0001
#> [1] 1e-04
0.00001
#> [1] 1e-05
options(scipen = 100)
1e5
#> [1] 100000
1e15
#> [1] 1000000000000000
0.00001
#> [1] 0.00001
1e-15
#> [1] 0.000000000000001
options(scipen = -5)
1000
#> [1] 1e+03
12345
#> [1] 12345
options(op)
tryCatch(options(scipen = NULL), error = \(e) conditionMessage(e))
#> [1] "option 'scipen' cannot be deleted"
tryCatch(options(digits = 23), error = \(e) conditionMessage(e))
#> [1] "invalid 'digits' parameter, allowed 1...22"

options(digits) interacts with the same decision: with digits = 3 a vector such as c(1.23456, 123456.789) shows both numbers in scientific notation because fixed notation would need many characters to keep three significant digits of the small one. A global option affects every print in the session (and the scripts of other packages), so prefer the local forms, print(x, digits = n), format(x, scientific = FALSE) and sprintf(), in libraries and reports, and save and restore the option when a script has to change it: old <- options(scipen = 100) then options(old).

# options(digits): significant digits shown by print() and format()
op <- options(digits = 3)
pi
#> [1] 3.14
1234.5678
#> [1] 1235
c(1.23456, 123456.789)
#> [1] 1.23e+00 1.23e+05
options(digits = 17)
0.1 + 0.2
#> [1] 0.30000000000000004
0.3
#> [1] 0.29999999999999999
1 / 3
#> [1] 0.33333333333333331
options(op)
pi
#> [1] 3.141593
getOption("OutDec")
#> [1] "."

The decimal mark and the grouping are locale matters. getOption("OutDec") is the decimal point used by printing, format() and formatC(); Encodings, Locales and Internationalization covers the locale side, including why a number printed in one locale can fail to parse in another.

Converting text to numbers

as.numeric() (the same function as as.double()) and as.integer() accept strings with optional blanks around them, decimal and scientific notation ("1e-3" is 0.001), hexadecimal with a 0x prefix ("0x1A" is 26), and the special strings "Inf", "-inf" and "NaN". Anything else gives NA with the warning NAs introduced by coercion: a thousands separator, a decimal comma ("1,5"), a currency sign, and even the string "NA", while the missing value NA_character_ converts silently. as.integer() of a string with a fraction truncates (as.integer("12.7") is 12), and as.numeric("TRUE") is NA although as.numeric(TRUE) is 1 (R documentation: numeric). The warning is the only signal of a bad value, so a data-cleaning script should count the new NA values, or catch the warning, as the chunk does.

The classic trap is a factor: as.numeric() of a factor returns the integer codes of the levels, not the labels, which the help page flags in a warning. Convert through the labels, as.numeric(as.character(f)), or through levels(f)[f]; R FAQ 7.10 has the same advice (R FAQ 7.10: How do I convert factors to numeric?). See Factors.

# Converting text to numbers
as.numeric("1e-3")
#> [1] 0.001
as.numeric("  12.5 ")
#> [1] 12.5
as.numeric("0x1A")
#> [1] 26
as.numeric("Inf")
#> [1] Inf
as.numeric("-inf")
#> [1] -Inf
as.numeric("NaN")
#> [1] NaN
tryCatch(as.numeric("NA"), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion"
as.numeric(NA_character_)
#> [1] NA
as.numeric("1e5")
#> [1] 1e+05
as.numeric(".5")
#> [1] 0.5
as.numeric("5.")
#> [1] 5
suppressWarnings(as.numeric("1_000"))
#> [1] NA
tryCatch(as.numeric("1,5"), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion"
tryCatch(as.numeric("abc"), warning = \(w) conditionMessage(w))
#> [1] "NAs introduced by coercion"
as.integer("12.7")
#> [1] 12
as.integer(" 42 ")
#> [1] 42
as.numeric(TRUE)
#> [1] 1
suppressWarnings(as.numeric("TRUE"))
#> [1] NA
as.numeric(factor(c("10", "20", "10")))
#> [1] 1 2 1
as.numeric(as.character(factor(c("10", "20", "10"))))
#> [1] 10 20 10
as.numeric(levels(factor(c("10", "20", "10"))))[factor(c("10", "20", "10"))]
#> [1] 10 20 10

The numbers of the shelf

The pieces above combine in the scenario’s late-fee calculation. The rule: a loan is overdue when it was out for more than loan_days (21) days, and the fee is 10 cents for each day past the period, with a ceiling of 250 cents a loan. The script keeps every amount in integer cents, so the arithmetic is exact and cannot overflow for any realistic shelf, converts to dollars only for display, and filters the two impossible rows (the missing date and the return before the loan) before summing. Open loans end at shelf_today, which is fixed so that the results are reproducible.

source("scenario.R")

# Days out: returned loans end at return_date, open loans at shelf_today
loans$end_date <- loans$return_date
open_loan <- is.na(loans$return_date) & !is.na(loans$loan_date)
loans$end_date[open_loan] <- shelf_today
loans$days_out <- as.integer(loans$end_date - loans$loan_date)
typeof(loans$days_out)
#> [1] "integer"
summary(loans$days_out)
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs
#>   -3.00   16.00   23.00   24.92   28.00   84.00       1
# Drop the impossible rows (missing dates, negative durations) before computing
valid <- !is.na(loans$days_out) & loans$days_out >= 0L
sum(valid)
#> [1] 58
sum(!valid)
#> [1] 2
# Days past the 21-day period: integer arithmetic stays exact
loans$days_late <- pmax(loans$days_out - loan_days, 0L)
typeof(loans$days_late)
#> [1] "integer"
table(loans$days_late[valid] > 0L)
#>
#> FALSE  TRUE
#>    26    32
max(loans$days_late[valid])
#> [1] 63

pmax() and pmin() apply the floor and the ceiling element-wise, keeping the integer type because both arguments are integers. The fees are integers too; the total of 3180 cents is an integer, and the %/% and %% operators split it into dollars and cents without any rounding question.

# Fees in integer cents: 10 cents a day, capped at 250 cents per loan
cents_per_day <- 10L
loans$fee_cents <- pmin(loans$days_late * cents_per_day, 250L)
typeof(loans$fee_cents)
#> [1] "integer"
total_cents <- sum(loans$fee_cents[valid])
total_cents
#> [1] 3180
typeof(total_cents)
#> [1] "integer"

Converting to dollars gives a double, and the sum of the doubles agrees with the total of the cents here, but the chunk compares with all.equal() anyway because it would not always. The presentation step shows the four ways to print the same number; sprintf("%d.%02d", ...) on the integer parts never touches a double.

# The same fees in dollars: the totals agree here, but compare with a tolerance
fee_dollars <- loans$fee_cents[valid] / 100
sum(fee_dollars) == total_cents / 100
#> [1] TRUE
isTRUE(all.equal(sum(fee_dollars), total_cents / 100))
#> [1] TRUE
print(sum(fee_dollars), digits = 17)
#> [1] 31.800000000000001
# Presenting the numbers
total_cents %/% 100L
#> [1] 31
total_cents %% 100L
#> [1] 80
sprintf("%d.%02d", total_cents %/% 100L, total_cents %% 100L)
#> [1] "31.80"
sprintf("%.2f", total_cents / 100)
#> [1] "31.80"
format(total_cents / 100, nsmall = 2)
#> [1] "31.80"
formatC(total_cents / 100, format = "f", digits = 2, big.mark = ",")
#> [1] "31.80"
paste0("$", formatC(total_cents / 100, format = "f", digits = 2))
#> [1] "$31.80"

Statistics of the loan lengths are doubles, so they are rounded only when displayed: the mean of the valid loan lengths is 25.39655 days, printed as 25.4, and sprintf("%.1f days (sd %.1f)") writes the pair into a sentence. The last chunk totals the fees per member after trimming and lower-casing the names (the names are deliberately messy in the data, see the Strings and Regular Expressions page) and prints them aligned, using %-12s for the name and %6.2f for the amount.

# Mean and spread of the loan lengths, rounded for display
days <- loans$days_out[valid]
mean(days)
#> [1] 25.39655
round(mean(days), 1L)
#> [1] 25.4
median(days)
#> [1] 23
round(sd(days), 2L)
#> [1] 17.08
quantile(days, c(0.25, 0.75))
#> 25% 75%
#>  16  28
sprintf("%.1f days (sd %.1f)", mean(days), sd(days))
#> [1] "25.4 days (sd 17.1)"
# A per-member summary (member names are trimmed and lower-cased first)
who <- trimws(tolower(loans$member[valid]))
by_member <- tapply(loans$fee_cents[valid], who, sum)
by_member <- by_member[names(by_member) != ""]
by_member <- sort(by_member, decreasing = TRUE)
top <- head(by_member, 4L)
cat(sprintf("%-12s %6.2f\n", names(top), top / 100), sep = "")
#> ana ruiz       8.70
#> chloe martin   6.00
#> dev patel      5.70
#> hugo silva     3.80

Common errors and warnings

The messages below were captured from R 4.6.1 by the scripts above. Most numeric problems are warnings and not errors, which means they pass unnoticed unless a script catches them or runs with options(warn = 2).

Message Raised by Cause and remedy Kind

NAs produced by integer overflow

+, -, * on integers

the result is outside plus or minus 2147483647; convert one operand to double (as.numeric()) or use a double from the start

warning

integer overflow in 'cumsum'; use 'cumsum(as.numeric(.))'

cumsum() on integers

the running total leaves the integer range; use cumsum(as.numeric(x))

warning

NAs introduced by coercion to integer range

as.integer() of a double or string above 2147483647, bitwAnd() of a large double

the value does not fit in 32 bits; keep it as a double, or split it with numToInts()

warning

NAs introduced by coercion

as.numeric(), as.integer() of a string

the string is not a number (thousands separator, decimal comma, the text "NA"); clean the string first and count the new NA values

warning

non-integer value 2147483648L qualified with L; using numeric value

the parser

the L suffix on a number that is not an integer or does not fit; drop the L

warning

integer literal 1.5L contains decimal; using numeric value

the parser

a decimal point in an L literal that is not a whole number

warning

integer literal 1.L contains unnecessary decimal point

the parser

a whole number written with a point and an L; write 1L

warning

NaNs produced

sqrt(), log(), asin(), gamma() outside their domain

the real function is undefined for the argument; make the argument complex (as.complex()) or check the domain first. The operator ^ returns NaN for a negative base with a fractional exponent without this warning

warning

probable complete loss of accuracy in modulus

%% with a dividend far larger than the divisor

the remainder of a huge double is meaningless; work with smaller values or with integers

warning

argument is not interpretable as logical

if (all.equal(x, y))

all.equal() returned a character vector; use isTRUE(all.equal(x, y))

error

invalid comparison with complex values

<, >, <=, >= on complex numbers

complex numbers are not ordered; compare Mod() or Re()

error

invalid 'type' (complex) of argument

max(), min(), range() on complex numbers

same cause; order with Mod()

error

no non-missing arguments to max; returning -Inf

max() of an empty vector (min() returns Inf)

the input is empty; check length(x) or pass a default value as an extra argument

warning

invalid format '%d'; use format %f, %e, %g or %a for numeric objects

sprintf("%d", 3.5)

%d needs a whole number; use %f, or round and convert first

error

invalid format '%,d'; use format %d, %i, %o, %x or %X for integer objects

sprintf("%,d", 1000L)

sprintf() has no thousands flag; use format(big.mark = ",") or formatC()

error

'big.mark' and 'decimal.mark' are both '.', which could be confusing

format(), prettyNum() with big.mark = "."

set decimal.mark = "," as well

warning

'format' must be one of {"f","e","E","g","G", "fg", "s"}

formatC(format = "%") and other invalid codes

use one of the listed formats (and "d" for integers)

error

unsupported type (with a trailing blank)

formatC(NA)

a logical NA cannot be formatted; use NA_real_ or NA_integer_

error

invalid 'digits' parameter, allowed 1...22

options(digits = 23)

digits must be between 1 and 22

error

option 'scipen' cannot be deleted

options(scipen = NULL)

set a number, usually 0 to restore the default

error

Unsupported object

bitstring() of a character or logical vector

only integer, double and raw vectors are supported; convert first

error

Version notes

The changes that matter for numbers, from the NEWS files. The scripts on this page ran on R 4.6.1; the notes say when a behaviour began.

Version Change Effect on code

R 3.0.0

bitwNot(), bitwAnd(), bitwOr(), bitwXor(), bitwShiftL() and bitwShiftR() added

bitwise work on integers no longer needs %/% and %% loops

R 3.0.2

sum() of integers uses an accumulator of at least 64 bits

more accurate sums of very long integer vectors

R 3.5.0

sum() of logical or integer vectors no longer overflows to NA; it returns a double when the total does not fit

the warning integer overflow - use sum(as.numeric(.)) disappears; cumsum() still warns

R 3.6.0

strtoi("", base) returns NA on all platforms

empty strings are missing values

R 3.6.2

x %% L for finite x no longer returns NaN when L is infinite

3 %% Inf is 3

R 4.0.0

round() measures the candidates and rounds to nearest even; negative digits is more rational; format(scientific = FALSE) is the most extreme scipen; .Machine gets longdouble.* entries when R uses long doubles

results of round(x, d) near a tie differ from earlier versions

R 4.1.0

numToBits() and numToInts() added; packBits(type = "double") inverts numToBits()

bit-level access to doubles in base R

R 4.1.1

all.equal() is TRUE correctly for entries close to .Machine$double.xmax

fewer false differences near the top of the range

R 4.4.0

as.complex("1i") is 0+1i; as.complex(x) keeps a zero imaginary part for NA; complex printing no longer zaps small parts

exp(1i * pi) prints -1+1.224647e-16i

R 4.5.0

options(scipen = NULL) and other invalid values signal an error; values outside -9 to 9999 are warned about

set scipen to a number in range

R 4.6.0

bitstring() added

a readable view of the bits of integers, doubles and raw vectors

The entries for R 3.x are in the separate NEWS file for R 3 (R NEWS: Changes in R 3.0.0, 3.0.2, 3.5.0, 3.6.0 and 3.6.2).

Where older tutorials differ

Many pages on the web predate these changes. When one of them disagrees with this page, the table says why.

Older tutorial Current practice Why Version

round(2.5) is 3, because "5 rounds up"

round(2.5) is 2; use floor(x + 0.5) only if you really want half up

R follows the IEC 60559 rule of going to the even digit

always

round(x, 2) gives the same digits as sprintf("%.2f", x)

round once with round(), then format; expect the two to differ close to a tie

the algorithm measures the candidates, whereas sprintf() rounds the exact binary value

R 4.0.0

sum(x) of integers overflows with a warning

it returns a double when needed; only cumsum() still overflows

the accumulator is wider than 32 bits

R 3.5.0

print(exp(1i * pi)) shows -1+0i

it shows -1+1.224647e-16i; round for display

printing no longer zaps small parts of complex numbers

R 4.4.0

as.complex("1i") is NA

it is 0+1i

the parser of as.complex() accepts the imaginary unit

R 4.4.0

is.integer(x) tells you whether x is a whole number

abs(x - round(x)) < tol

is.integer() tests the storage type

always

format(1e6, big.mark = ",") gives "1,000,000"

format(1e6, big.mark = ",", scientific = FALSE) or formatC(1e6, format = "d", big.mark = ",")

the scientific string is shorter, so format() keeps it

always

options(scipen = 999) at the top of every script

format(scientific = FALSE) or sprintf() where needed

the option changes every print in the session

always; errors for invalid values since R 4.5.0

(-8)^(1/3) should be -2, or “R FAQ 7.33 explains NaN”

use a real cube root function, or the complex principal root; FAQ 7.33 is about -2^2

^ follows the C99 pow, which gives NaN for a negative base and a fractional exponent

always

seq(0, 1, by = 0.1) == 0.3 finds the element

compare with a tolerance

the fourth element is 0.30000000000000004 (R FAQ 7.31)

always

as.numeric(f) converts a factor of numbers

as.numeric(as.character(f))

it returns the integer codes of the levels (R FAQ 7.10)

always

bit manipulation with %/%, %% and powers of two, or with the bitops package

bitwAnd() and friends, bitstring() to look at the result

the bitwise functions are in base R

R 3.0.0; bitstring() R 4.6.0

.Machine$double.eps is the tolerance for every comparison

a relative tolerance such as sqrt(.Machine$double.eps) in all.equal()

double.eps is the gap only near 1

always

For the full list of changes by release see What’s New and Migrating Older Code.

Where to go next

References