Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

R (4.6)  Module 2 – Vectors, types and missing values

Numbers, floating point and rounding

Why 0.1 + 0.2 == 0.3 is FALSE in R, how to compare doubles with all.equal(), how round() treats halves, where integers overflow, and how to format numbers.

  • Intermediate
  • 10 minutes
  • Examples run with R 4.6.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Explain why 0.1 + 0.2 == 0.3 is FALSE and compare doubles safely
  • Predict R's rounding of halves and of numbers that are not exactly representable
  • Recognise integer overflow, the limits of doubles and platform-dependent results
  • Format numbers for display without changing the stored values

Before you start

What you’ll learn · Before you start

What you will learn

  • Explain why 0.1 + 0.2 == 0.3 is FALSE and compare doubles safely
  • Predict R's rounding of halves and of numbers that are not exactly representable
  • Recognise integer overflow, the limits of doubles and platform-dependent results
  • Format numbers for display without changing the stored values

Before you start

On this page

Type 0.1 + 0.2 == 0.3 into R and the answer is FALSE, although 0.1 + 0.2 prints as 0.3. Nothing is broken: R stores numbers as binary fractions, most decimal numbers have no exact binary form, and the same fact explains why round(2.675, 2) is 2.67 and why three shares of 1,000.00, each rounded to 333.33, add up to 999.99.

On runs of R 4.6.1 you will see what R stores for a number and compare doubles safely, predict how round() treats halves, recognise where integers overflow and doubles stop counting by ones, and print numbers in any format without changing the values that calculations use.

Doubles are binary fractions, which is why 0.1 plus 0.2 is not 0.3

A number such as 0.1 is stored as a double: R's type for real numbers: a binary fraction with 53 bits of precision, about 15 significant decimal digits, in the IEC 60559 (IEEE 754) format. Most decimals, such as 0.1, are stored as the nearest such fraction.: a binary fraction with 53 bits of precision, in the IEC 60559 format that the help page of double says every R platform uses. A fraction whose denominator, in lowest terms, is not a power of 2, such as 1/10, has no exact binary form, just as 1/3 has no exact decimal one, so R stores the double nearest to it. Step through the four numbers of the comparison:

Interactive · step by step Why 0.1 + 0.2 misses 0.3 Step through what R stores for each number, to twenty decimal places, then the comparison.
Why 0.1 + 0.2 misses 0.3, step 6 of 6. Showing 0.1 (0.10000000000000000555), 0.2 (0.20000000000000001110), 0.1 + 0.2 (0.30000000000000004441), 0.3 (0.29999999999999998890), == (all.equal: TRUE). In focus: ==.0.10.100000000000000005550.20.200000000000000011100.1 + 0.20.300000000000000044410.30.29999999999999998890==all.equal: TRUE

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. 0.1 in binary. A double is a binary fraction of 53 bits. 0.1 has no exact binary form, as 1/3 has no exact decimal one, so R stores the nearest double, a little above 0.1.
  2. 0.2. 0.2 is stored a little above 0.2 as well.
  3. The sum. Adding the two stored values and rounding to the nearest double gives a number a little above 0.3.
  4. 0.3 itself. Typed directly, 0.3 is stored a little below 0.3. The sum and this value are neighbouring doubles, 2^-54 apart, about 5.55e-17.
  5. The comparison. == compares the stored values, which differ, so 0.1 + 0.2 == 0.3 is FALSE. Printing rounds both to 7 digits, 0.3, which hides the difference.
  6. With a tolerance. all.equal() accepts differences smaller than about 1.5e-8, measured relative to the size of the numbers, so isTRUE(all.equal(0.1 + 0.2, 0.3)) is TRUE.

The run prints the same four values to twenty decimal places with sprintf(), and then the comparisons:

binary.R, typed at the R console Python · binary_session.py
from session import run, version

version()
# R reads binary.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f binary.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f binary.R
> # The sum prints as 0.3, but it is not 0.3.
> 0.1 + 0.2
[1] 0.3
> 0.1 + 0.2 == 0.3
[1] FALSE
> # Twenty decimal places show the stored values.
> sprintf("%.20f", c(0.1, 0.2, 0.1 + 0.2, 0.3))
[1] "0.10000000000000000555" "0.20000000000000001110" "0.30000000000000004441"
[4] "0.29999999999999998890"
> (0.1 + 0.2) - 0.3
[1] 5.551115e-17
> # They are neighbouring doubles, 2^-54 apart.
> (0.1 + 0.2) - 0.3 == 2^-54
[1] TRUE
> # The same happens inside a sequence.
> seq(0, 1, by = 0.1)[4] == 0.3
[1] FALSE
> # Compare with a tolerance instead.
> all.equal(0.1 + 0.2, 0.3)
[1] TRUE
> isTRUE(all.equal(0.1 + 0.2, 0.3))
[1] TRUE
> abs((0.1 + 0.2) - 0.3) < 1e-9
[1] TRUE
> # A real difference is reported, not hidden.
> all.equal(1, 1.1)
[1] "Mean relative difference: 0.1"
> isTRUE(all.equal(1, 1.1))
[1] FALSE

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 binary_session.py

The sum lands on the double just above 0.3 and the literal 0.3 on the one just below, 2^-54 apart, which the run confirms. The fourth value of seq(0, 1, by = 0.1) carries the same kind of error, so comparing it with 0.3 fails too.

The fix is to compare with a tolerance. all.equal(x, y) accepts differences smaller than about 1.5e-8, measured relative to the size of the numbers, and returns TRUE or a sentence that describes the difference, so inside if () write isTRUE(all.equal(x, y)). When you know the scale of your data, abs(x - y) < tol with your own tol says the same thing explicitly.

Testing doubles with ==

if (total == 0.3) can fail on a value that prints as 0.3. Use isTRUE(all.equal(total, 0.3)), or abs(total - 0.3) < 1e-9 when you know the scale. Keep == for integers and for values you set yourself.

IEEE 754 Floating-Point Converter See every bit of the double that stores 0.1, 0.3 or any number you type.

Checkpoint

Check: Doubles as binary fractions

  1. Question 1 of 2 Why does 0.1 + 0.2 == 0.3 return FALSE in R?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: 0.1, 0.2 and 0.3 are stored as the nearest binary fractions, and the sum lands on a different double from 0.3

    Neither 0.1 nor 0.2 nor 0.3 has an exact binary form. Their stored values differ slightly from the decimals, and the stored sum and the stored 0.3 are neighbouring doubles, 2^-54 apart.

  2. Question 2 of 2 total <- 0.1 + 0.2. Which condition is TRUE?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: isTRUE(all.equal(total, 0.3))

    Compare doubles with a tolerance: isTRUE(all.equal(x, y)), or abs(x - y) < tol when you know the scale.

round() goes to the even digit, but only for halves it can see

round(c(0.5, 1.5, 2.5)) returns 0 2 2. For a half, the help page of round() says R expects the IEC 60559 rule: go to the even digit, so 0.5 rounds down to 0 and 1.5 up to 2. Halves then round down as often as up, so a long column of rounded values does not drift upwards. But a decimal “half” such as 2.675 is usually not stored as a half at all:

Interactive · step by step Where 2.675 really sits Step through round(2.675, 2). The column on the left is a short stretch of the number line, with larger values at the top.
Where 2.675 really sits, step 4 of 4. Showing 2.68, 2.675, halfway, 2.675 as stored (2.67499999999999982236), 2.67, round(x, 2) (returns 2.67). In focus: 2.675, halfway.2.682.675, halfway2.675 as stored2.674999999999999822362.67round(x, 2)returns 2.67not stored, so not used

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. Two neighbours. Rounding 2.675 to two decimals chooses between 2.67 and 2.68, and 2.675 is exactly halfway between them.
  2. What R stores. 2.675 has no exact binary form, and the double R stores for it is a little below halfway.
  3. round(2.675, 2). Since R 4.0.0, round() measures which neighbour the stored value is nearer, and it is nearer 2.67. So round(2.675, 2) is 2.67, and no rule about halves is involved.
  4. Only true halves go to even. The even-digit rule decides only halves that are stored exactly, such as 2.5 or 0.125. For a decimal like 2.675, the stored value decides.

The run puts true halves beside decimal ones, with the stored values printed to twenty places:

rounding.R, typed at the R console Python · rounding_session.py
from session import run, version

version()
# R reads rounding.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f rounding.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f rounding.R
> # A half that is stored exactly goes to the even digit.
> round(c(0.5, 1.5, 2.5, -0.5, -1.5))
[1]  0  2  2  0 -2
> round(0.125, 2)
[1] 0.12
> # Most decimal "halves" are stored a little off.
> round(2.675, 2)
[1] 2.67
> sprintf("%.20f", 2.675)
[1] "2.67499999999999982236"
> round(0.15, 1)
[1] 0.1
> sprintf("%.20f", 0.15)
[1] "0.14999999999999999445"
> # The other ways to round.
> ceiling(2.1)
[1] 3
> floor(-2.1)
[1] -3
> trunc(-2.7)
[1] -2
> signif(123456, 2)
[1] 120000
> round(123456, -3)
[1] 123000

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rounding_session.py

0.125 is 1/8, a binary fraction, so it is stored exactly, it is a true half, and round(0.125, 2) goes to the even digit, 0.12. 2.675 and 0.15 are stored a little below their printed values, so they round down, to 2.67 and 0.1. The help page warns about exactly this: the rounding rule applies to the represented number, not the printed one.

ceiling() rounds up, floor() down, trunc() towards zero, signif() to a number of significant digits, and a negative digits rounds to tens, hundreds or thousands: round(123456, -3) is 123000.

Key idea

round() works on the stored value: a true half goes to the even digit, and a decimal that only looks like a half rounds to whichever neighbour its stored value is nearer.

Rounding Calculator Compare half to even with the other rounding modes, side by side, on your own numbers.

Checkpoint

Check: How round() treats halves

  1. Question 1 of 2 What does this return?

    Read the code, then choose one answer.

    round(c(2.5, 3.5))
    Show the answer: Question 1 of 2

    Answer: 2 4

    For a true half, round() goes to the even digit, as the IEC 60559 standard expects: 2.5 becomes 2 and 3.5 becomes 4.

  2. Question 2 of 2 sprintf("%.20f", 1.005) is "1.00499999999999989342". What does round(1.005, 2) return?

    Choose one answer.

    A hint

    round() looks at the stored value.

    Show the answer: Question 2 of 2

    Answer: 1

    The double stored for 1.005 is just below 1.005, so round() goes to 1.00. Only a half stored exactly, such as 0.125, uses the even-digit rule.

Integers overflow to NA, and doubles stop counting by ones at 2^53

R’s integers are 32-bit, so .Machine$integer.max is 2,147,483,647, and one more is out of range. The help page of integer says that as.integer() turns a value beyond the range into NA, and integer arithmetic does the same; both warn, as the run shows. Doubles have the opposite trade-off: a far larger range, about 2e+308, with 53 bits of precision, so whole numbers are exact only up to 2^53, 9,007,199,254,740,992.

limits.R, typed at the R console Python · limits_session.py
from session import run, version

version()
# R reads limits.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f limits.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f limits.R
> # Integers are 32-bit: this is the largest one.
> .Machine$integer.max
[1] 2147483647
> .Machine$integer.max + 1L
[1] NA
Warning message:
In .Machine$integer.max + 1L : NAs produced by integer overflow
> as.integer(2^31)
[1] NA
Warning message:
NAs introduced by coercion to integer range 
> # The same sum in doubles is no problem.
> .Machine$integer.max + 1
[1] 2147483648
> # Doubles hold whole numbers exactly up to 2^53.
> format(2^53, scientific = FALSE)
[1] "9007199254740992"
> 2^53 == 2^53 + 1
[1] TRUE
> # The gap between neighbouring doubles grows with size.
> 2^(floor(log2(c(1, 1e6, 2^53))) - 52)
[1] 2.220446e-16 1.164153e-10 2.000000e+00
> .Machine$double.eps
[1] 2.220446e-16

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 limits_session.py

The second-to-last line shows why. The gap between one double and the next grows with the size of the number: about 2.2e-16 near 1, about 1.2e-10 near a million, and 2 at 2^53, so 2^53 + 1 has nowhere to go and rounds back to 2^53. .Machine$double.eps, 2.220446e-16, is that gap at 1: its help page defines it as the smallest positive number x for which 1 + x != 1.

Use a double when a sum may pass two billion

Counts, ids and money stored as integers overflow at 2,147,483,647. .Machine$integer.max + 1, with a double 1, gives 2147483648 without a warning, because the sum is a double. Convert with as.numeric() before summing large integer columns.

Checkpoint

Check: Integer and double limits

  1. Question 1 of 2 What does .Machine$integer.max + 1L return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: NA, with a warning about integer overflow

    R's integers are 32-bit, up to 2,147,483,647. Integer arithmetic beyond that gives NA with a warning; the same sum with a double is fine.

  2. Question 2 of 2 What does 2^53 + 1 == 2^53 return?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: TRUE

    Doubles hold whole numbers exactly only up to 2^53. Beyond that, adding 1 can be lost entirely.

The last digits can differ between computers, so never test them

sum(rep(0.1, 10)) == 1 is FALSE in the run below, on an Apple-silicon Mac. The help page of sum() says it uses extended-precision accumulators where possible, which is platform-dependent, and capabilities("long.double") says whether this build of R has a long double wider than a double. Writing R Extensions adds that current ARM CPUs have neither extended precision nor long doubles:

platform.R, typed at the R console Python · platform_session.py
from session import run, version

version()
# R reads platform.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f platform.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f platform.R
> # Does this build of R have a long double wider than a double?
> capabilities("long.double")
long.double 
      FALSE 
> # sum() adds in long double where there is one; here there is not.
> sum(rep(0.1, 10)) == 1
[1] FALSE
> print(sum(rep(0.1, 10)), digits = 17)
[1] 0.99999999999999989
> # A loop adds in plain doubles on every computer.
> total <- 0
> for (v in rep(0.1, 10)) total <- total + v
> print(total, digits = 17)
[1] 0.99999999999999989
> # A check that gives the same answer on every computer.
> isTRUE(all.equal(sum(rep(0.1, 10)), 1))
[1] TRUE
> # Exact at special values on every platform since R 4.6.0.
> exp(0) == 1
[1] TRUE
> log1p(0) == 0
[1] TRUE

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 platform_session.py

On this Apple-silicon build there is no long double, so sum() adds in plain doubles, exactly as the loop does, and both reach 0.99999999999999989. On a build where capabilities("long.double") is TRUE, sum() adds the same ten values in a wider format, and the same comparison can come out TRUE. Code whose result depends on that last bit behaves differently from one computer to the next; code that compares with all.equal() does not.

New in R 4.6.0: exact results at special values

As an experiment, R 4.6.0 made several maths functions, among them exp() and log1p(), give fully accurate results near special values on every platform, so exp(0) == 1 and log1p(0) == 0 hold even where the system’s maths library is less accurate. Its NEWS entry points to the R blog post in the references, on how one-unit differences between platforms’ maths libraries break tests whose tolerances are too tight.

Checkpoint

Check: Results that depend on the computer

  1. Question 1 of 2 capabilities("long.double") is FALSE on a computer. What does that tell you about sum(rep(0.1, 10)) there?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: sum() adds in plain doubles, so it gives the same last digits as a loop that adds one value at a time

    sum() uses extended-precision accumulators where they exist. On a build without a long double, it adds in plain doubles, the way a loop does, and gives 0.99999999999999989 for this sum.

  2. Question 2 of 2 A test must give the same verdict on every computer that the weights w add up to 1. Which line?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: stopifnot(isTRUE(all.equal(sum(w), 1)))

    Tests must never depend on the last bits of a double. A tolerance makes the verdict the same everywhere.

format(), formatC() and sprintf() change the print, not the value

x <- 1234567.891 prints as 1234568, because R shows 7 significant digits by default. Printing is a view, never a change: format(), formatC() and sprintf() return text for display, and options(digits = 4) changes how numbers print, while x itself keeps every digit it had.

formatting.R, typed at the R console Python · formatting_session.py
from session import run, version

version()
# R reads formatting.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f formatting.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f formatting.R
> x <- 1234567.891
> # Printing shows 7 significant digits by default.
> x
[1] 1234568
> # format(), formatC() and sprintf() return text.
> format(x, nsmall = 2)
[1] "1234567.89"
> formatC(x, format = "f", digits = 2, big.mark = ",")
[1] "1,234,567.89"
> sprintf("%.2f", x)
[1] "1234567.89"
> # options(digits =) changes printing, nothing else.
> options(digits = 4)
> 1 / 3
[1] 0.3333
> options(digits = 7)
> # The stored value never changed.
> x == 1234567.891
[1] TRUE
> # An amount of 1,000.00 in its smallest unit, split three ways.
> total <- 100000L
> share <- rep(total %/% 3L, 3)
> extra <- seq_len(total %% 3L)
> share[extra] <- share[extra] + 1L
> share
[1] 33334 33333 33333
> sum(share) == total
[1] TRUE
> sprintf("%.2f", share / 100)
[1] "333.34" "333.33" "333.33"
> # Rounding each share instead loses one unit.
> naive <- rep(round(1000 / 3, 2), 3)
> naive
[1] 333.33 333.33 333.33
> sum(naive)
[1] 999.99

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 formatting_session.py

  • format(x, nsmall = 2) keeps at least 2 decimals, "1234567.89".
  • formatC(x, format = "f", digits = 2, big.mark = ",") adds thousands separators, "1,234,567.89".
  • sprintf("%.2f", x) uses C’s formats, the same as in many other languages, "1234567.89".
  • options(digits = 4) changes every later print, here 1 / 3 as 0.3333, until you set it back.

The second half of the run shows how to keep money exact. An amount of 1,000.00 held as 100000 of its smallest unit, an integer, splits three ways with integer division, %/%, and the remainder, %%, goes to the first share: 333.34, 333.33 and 333.33, which add up exactly. Rounding each share to two decimals gives 333.33 three times and loses one unit. Keep amounts in whole minor units, compute with them, and format them only for display.

Paise, and the Indian digit grouping

The same split works in paise: ₹1,000 is 100000 paise. formatC(big.mark = ",") groups digits in threes, the international way, so 1234567.89 prints as 1,234,567.89. The Indian grouping writes it as 12,34,567.89, which format() and formatC() cannot produce, since their big.mark separates digits at one fixed interval, so build that string yourself, or leave the display to a tool that knows it.

Lakh Crore Converter (to Million, Billion) Convert between the Indian and the international digit groupings, and into words.

Checkpoint

Check: Formatting without changing values

  1. Question 1 of 2 What does formatC(2.5, format = "f", digits = 2) return?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The text "2.50"

    format(), formatC() and sprintf() all return character strings. Use them for display, after every calculation is done.

  2. Question 2 of 2 share <- rep(100L %/% 3L, 3), and the remainder 100L %% 3L is added to the first share. What is the first share?

    Type a number.

    Show the answer: Question 2 of 2

    Answer: 34

    100 %/% 3 is 33 and 100 %% 3 is 1, so the shares are 34, 33 and 33, which add up to exactly 100.

Practise: predict, then check

The last set of lines mixes binary fractions, halves, integer limits and printing. Commit to an answer for each.

Predict the six results

With x <- 0.3 - 0.1, predict x == 0.2 and isTRUE(all.equal(x, 0.2)); then round(c(1.5, 2.5, 3.5)), round(1.005, 2), .Machine$integer.max + 1 and format(2/3, digits = 3). Then run them and compare.

practice.R, typed at the R console Python · practice_session.py
from session import run, version

version()
# R reads practice.R as if each line were typed at the console.
run("""
R --vanilla --quiet -f practice.R
""")

Output

# R version 4.6.1 -- "Happy Hop"
$ R --vanilla --quiet -f practice.R
> x <- 0.3 - 0.1
> x == 0.2
[1] FALSE
> isTRUE(all.equal(x, 0.2))
[1] TRUE
> round(c(1.5, 2.5, 3.5))
[1] 2 2 4
> round(1.005, 2)
[1] 1
> .Machine$integer.max + 1
[1] 2147483648
> format(2/3, digits = 3)
[1] "0.667"

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 practice_session.py

0.3 - 0.1 misses 0.2 for the same reason 0.1 + 0.2 misses 0.3, and all.equal() accepts it; the halves went to the even digits 2, 2 and 4; 1.005 is stored just below 1.005, so it rounds to 1; adding a double 1 to the largest integer gives a double, with no overflow; and format() rounded the printed text, not the value.

Last, the ideas of this page meet in one adaptive round: the weakest idea is asked first, each answer comes with its reason, and a question you miss returns once more before the summary:

Practice that adapts to you

Practise this lesson

14 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 14 How many significant digits does R print by default?

    Type a number.

    Show the answer: Question 1 of 14

    Answer: 7 digits

    The option digits is 7 by default, so 0.30000000000000004 prints as 0.3 and 1234567.891 as 1234568. The stored value keeps all its digits.

  2. Question 2 of 14 x <- seq(0, 1, by = 0.1). Which line finds the position of 0.3 in x?

    Choose one answer.

    Show the answer: Question 2 of 14

    Answer: which(abs(x - 0.3) < 1e-9)

    Exact matching fails on computed doubles. Compare with a tolerance that fits the scale of the data.

  3. Question 3 of 14 round(0.125, 2) is 0.12 and round(2.675, 2) is 2.67. Which explanation is right?

    Choose one answer.

    Show the answer: Question 3 of 14

    Answer: 0.125 is stored exactly, so the even-digit rule decides; 2.675 is stored below its printed value, so it rounds down

    round() sees the stored value. A true half goes to the even digit; a decimal that only looks like a half rounds to its nearer neighbour.

  4. Question 4 of 14 What does round(0.5) return?

    Choose one answer.

    Show the answer: Question 4 of 14

    Answer: 0

    The help page of round() gives this very case: round(0.5) is 0 and round(-1.5) is -2.

  5. Question 5 of 14 What does this return?

    Read the code, then choose one answer.

    round(c(-2.5, -3.5))
    Show the answer: Question 5 of 14

    Answer: -2 -4

    The even-digit rule is the same for negative numbers: -2.5 goes to -2 and -3.5 to -4.

  6. Question 6 of 14 What is .Machine$integer.max?

    Type a number.

    Show the answer: Question 6 of 14

    Answer: 2147483647

    2,147,483,647, the largest 32-bit signed integer, which is 2^31 - 1. as.integer(2^31) is NA.

  7. Question 7 of 14 What does as.integer(3e9) return?

    Choose one answer.

    Show the answer: Question 7 of 14

    Answer: NA, with a warning

    Real values larger than the largest integer are coerced to NA, with the warning "NAs introduced by coercion to integer range".

  8. Question 8 of 14 The newest order id is stored as the integer 2147483600L. What happens to last_id + seq_len(100)?

    Choose one answer.

    Show the answer: Question 8 of 14

    Answer: The first 47 ids are fine and the other 53 become NA, with a warning

    2147483600 + 47 is 2147483647, the largest integer, and every sum after that overflows. Store large ids as doubles, or as text.

  9. Question 9 of 14 Which call tells you whether this build of R has a long double wider than a double?

    Choose one answer.

    Show the answer: Question 9 of 14

    Answer: capabilities("long.double")

    capabilities("long.double") is TRUE when the build has a long double wider than a double, and FALSE on builds such as R on Apple silicon.

  10. Question 10 of 14 On a build where capabilities("long.double") is TRUE, can sum(x) and a loop that adds x one value at a time give different last digits?

    Choose one answer.

    Show the answer: Question 10 of 14

    Answer: Yes, because sum() may keep its running total in the wider long double

    With a long double, sum() adds in a wider format and rounds once at the end, while a loop rounds after every addition, so the last digits can differ.

  11. Question 11 of 14 A result must match a reference value that was computed on another computer. Which check is portable?

    Choose one answer.

    Show the answer: Question 11 of 14

    Answer: isTRUE(all.equal(result, reference))

    Compare computed doubles with a tolerance, never bit for bit, when they come from different computers.

  12. Question 12 of 14 options(digits = 3), then x <- pi. What is stored in x?

    Choose one answer.

    Show the answer: Question 12 of 14

    Answer: The full double pi, 3.141592653589793 to 16 digits

    The digits option is a printing setting. The recorded run printed 1/3 as 0.3333 with digits = 4, while x kept every digit it had.

  13. Question 13 of 14 What does this return?

    Read the code, then choose one answer.

    typeof(format(2.5, nsmall = 2))
    Show the answer: Question 13 of 14

    Answer: "character"

    format(), formatC() and sprintf() return character vectors. Calculating with their results would need as.numeric() first, which is a sign that formatting happened too early.

  14. Question 14 of 14 An amount of 100.00 is split three ways with round(100 / 3, 2) for each share. What do the three shares add up to?

    Type a number.

    Show the answer: Question 14 of 14

    Answer: 99.99 (anything from 99.989 to 99.991 counts)

    Each share is 33.33, and three of them make 99.99: one unit is lost. In whole minor units, 10000 %/% 3 and the remainder give 33.34, 33.33 and 33.33, which add up exactly.

Interview questions

Warm-up: why is 0.1 + 0.2 == 0.3 FALSE in R, and how should you compare such numbers?

R keeps a number such as 0.1 as a double, a binary fraction with 53 bits of precision. None of 0.1, 0.2 and 0.3 can be written as such a fraction, so R keeps the closest double to each, and the three small errors do not cancel: 0.1 + 0.2 ends up as 0.30000000000000004441 and 0.3 as 0.29999999999999998890, neighbouring doubles about 5.55e-17 apart. == compares the stored values, so it says FALSE, while printing to 7 digits shows both as 0.3. Compare with a tolerance instead: isTRUE(all.equal(a, b)), which accepts relative differences below about 1.5e-8, or abs(a - b) < tol with a tol that suits the scale of the data. The same applies in every language that uses IEEE 754 doubles; it is a property of binary floating point, not of R.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    An invoice of 1,000.00 is split equally among three people with round(1000 / 3, 2) each, and the shares add up to 999.99. Explain what went wrong, and how you would compute shares that always add up to the total, for any amount and any number of people.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    A data check, stopifnot(sum(weights) == 1), passes on a colleague's x86-64 Linux server but stops on your Apple-silicon Mac with the same weights. Explain why the two computers disagree, and how you would write the check so that it gives the same verdict everywhere.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A double is a binary fraction with 53 bits of precision, so most decimals are stored inexactly and 0.1 + 0.2 == 0.3 is FALSE; compare with isTRUE(all.equal(x, y)) or abs(x - y) < tol.
  • round() sends a true half to the even digit, round(2.5) is 2, while a decimal like 2.675 rounds by its stored value, so round(2.675, 2) is 2.67.
  • Integers stop at 2,147,483,647 and overflow to NA with a warning, while doubles count whole numbers exactly only up to 2^53.
  • The last bits can differ between computers, as sum() with and without a long double shows, so tests must never depend on them.
  • format(), formatC(), sprintf() and options(digits) change only how numbers print; keep money in whole minor units and format it at the end.

Terms in this lesson

double
R's type for real numbers: a binary fraction with 53 bits of precision, about 15 significant decimal digits, in the IEC 60559 (IEEE 754) format. Most decimals, such as 0.1, are stored as the nearest such fraction.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.