Numeric and Integer: R's Two Number Types
R stores numbers in two ways. Numeric (stored as a double-precision float) is the default - every number you type is a double unless you say otherwise. Integer is a separate exact whole-number type that you request with the L suffix:
In practice you rarely need to care: R converts between them silently, and is.numeric() is TRUE for both (see data types for the full type system). The one place the distinction shows through is division - / always returns a double, even between two integers:
Integers mostly appear as the output of counting functions (length(), seq_len(), nrow()) and as index values. When you need whole-number division, R has a dedicated operator - covered below.
The Rounding Family
R gives you five ways to trim a number, each with a distinct meaning:
round(x, digits)- to a number of decimal places (default 0).floor(x)- down to the nearest integer, always toward negative infinity.ceiling(x)- up, always toward positive infinity.trunc(x)- chops the decimals, always toward zero. Note the difference on negatives:trunc(-2.7)is-2, butfloor(-2.7)is-3.signif(x, digits)- to a number of significant figures, not decimal places:signif(123456, 2)is120000.
And one famous surprise: round() uses round half to even (banker's rounding) on exact halves, per the IEEE 754 standard:
That prints 0 2 2 4 - each half rounds to the nearest even number. It's not a bug; it prevents systematic upward bias when you sum lots of rounded values. If a report needs schoolbook rounding, add a tiny nudge or format at the presentation layer instead.
Everyday Math Functions
The basics work exactly as you'd guess:
sqrt() is the square root, abs() the absolute value, ^ is exponentiation, and exp(x) is e to the power x - so exp(1) is Euler's number, about 2.718282.
The one function that trips people up is log(). In R, log() is the natural logarithm (base e), not base 10:
The first line prints roughly 4.60517 - not the 2 a base-10 reader expects. Reach for log10() and log2() when you mean those bases, or pass base = explicitly. (This convention is standard across statistics, where the natural log is the default.)
Modulo %% and Integer Division %/%
Two operators cover remainder arithmetic:
%% is the modulo (remainder): 17 divided by 5 is 3 remainder 2. %/% is integer division: how many whole 5s fit in 17. Together they satisfy x == (x %/% y) * y + (x %% y).
The classic use of %% is testing divisibility:
One subtlety with negative numbers: R's %% takes the sign of the divisor (like Python, unlike C):
That's 2, not -1 - R answers "what do I add to a multiple of 3 to reach -7?", which keeps results in 0..2 for a positive divisor. Handy for wrapping indexes; surprising if you're coming from C or Java.
Special Values: Inf, -Inf, and NaN
R's numbers follow IEEE 754, so some operations produce special values instead of errors:
1/0 is Inf (infinity), -1/0 is -Inf, and 0/0 - a genuinely undefined quantity - is NaN, "not a number". The distinction matters: Inf is an answer ("larger than anything"), NaN is the absence of one. You test for them with dedicated functions, because == NaN never works:
Note the last line: NaN also counts as NA, so is.na() catches it - one more reason is.na() is the standard "is this value unusable?" check (more in missing values).
R also reads and writes scientific notation natively - 2.5e3 is 2500, and very small or large numbers print in e-notation by default:
Use format(x, scientific = FALSE) (or the scipen option) when a report needs plain decimals.
The Floating-Point Surprise
Every language that stores decimals in binary shares this one, and R is no exception:
FALSE - because 0.1 + 0.2 is actually 0.30000000000000004. Neither 0.1 nor 0.2 has an exact binary representation, and the tiny errors accumulate. R's default printing hides this by showing 7 significant digits, which is why the problem feels invisible until an == comparison fails.
The rule: never compare computed decimals with ==. Use all.equal(), which compares within a sensible tolerance:
Wrap it in isTRUE() because all.equal() returns a description of the difference (not FALSE) when values differ. For whole-number work where exactness matters, integers are exact up to about 2.1 billion - another reason counting code uses the integer type.
What You Take Away
- Every typed number is a double;
42Lmakes an integer, and/returns a double regardless. round()rounds half to even;floor/ceiling/trunc/signifeach trim differently - know which one you mean.log()is the natural log; uselog10(),log2(), orbase =for other bases.%%gives the remainder (sign follows the divisor),%/%the whole-number quotient.1/0isInf,0/0isNaN, and0.1 + 0.2 != 0.3- compare decimals withall.equal(), never==.
Next up: the other half of everyday data - strings, and the functions R gives you to build, format, and search them.
Frequently Asked Questions
What is the difference between numeric and integer in R?
Numeric (double) is R's default for any number you type - 42 is a double even though it looks whole. Integer is a separate storage type you request with an L suffix: 42L. Regular division always returns a double, even between integers; use %/% for integer division.
Is log() in R the natural log?
Yes - log(x) in R is the natural logarithm (base e), not base 10. Use log10() for base 10, log2() for base 2, or log(x, base = b) for any base. log(100) is about 4.605, not 2.
How does round() work in R?
round(x, digits) rounds to the given number of decimal places, but exact halves use "round half to even" (banker's rounding): round(2.5) is 2 and round(3.5) is 4. This follows the IEEE 754 standard and reduces bias when summing rounded values, but it surprises people expecting schoolbook rounding.
Why is 0.1 + 0.2 not equal to 0.3 in R?
Doubles are stored in binary, and 0.1, 0.2, and 0.3 have no exact binary representation, so 0.1 + 0.2 is actually 0.30000000000000004. Never compare computed decimals with ==; use isTRUE(all.equal(x, y)) or check abs(x - y) < 1e-9.