19

This works:

> sprintf('%d', c(1, 1.5))
[1] "1" "1"

and this doesn't:

> sprintf('%d', c(1.5, 1))
Error in sprintf("%d", c(1.5, 1)) : 
  invalid format '%d'; use format %f, %e, %g or %a for numeric objects

Why?

Mikael Jumppanen
  • 2,436
  • 1
  • 17
  • 29
pomber
  • 23,132
  • 10
  • 81
  • 94
  • I know I could use something like `sprintf('%.f', c(1.5, 1))` but I want to understand what is happening with the `%d` – pomber Nov 24 '14 at 14:53
  • 3
    I'm meditating over [sprintf.c](https://svn.r-project.org/R/trunk/src/main/sprintf.c), so far it's hard to say exactly why the behaviour is asymmetric. There's a comment about coercion attempt at `ns = 0`, though the code around it is cryptic. – tonytonov Nov 24 '14 at 15:28
  • @RichardScriven what is in the help file? – pomber Nov 24 '14 at 22:00
  • Possible duplicate: http://stackoverflow.com/questions/10786169/why-causes-invalid-format-d-in-r – PascalVKooten Nov 25 '14 at 08:44
  • @PascalvKooten That doesn't answer why the first case works. Read my first comment – pomber Nov 25 '14 at 12:21
  • I see the same error when the integer is very large. i.e Even though 3e9 is an integer. I get the error when `sprintf("Number of Iterations:\t %d",3e9)`. – DKangeyan May 07 '18 at 22:34

1 Answers1

12

This is actually really interesting question. To start, %d stands for integer. The vector argument is recycled if possible but if it is c(1.5, 1) it will fail when sprintf() tries to replace %d with 1.5 (which is not integer).

I thought it might be related to the fact that in R both integer and double are numeric mode, for example:

storage.mode(c(1.5, 1))
# [1] "double"
storage.mode(c(1, 1.5))
# [1] "double"
mode(c(1,1.5))
# [1] "numeric"
mode(c(1.5,1))
# [1] "numeric"

Thus both vectors should be stored as double. More info about vector in R language definition and in the documentation for ? numeric:

The potential confusion is that R has used mode "numeric" to mean ‘double or integer’"

I might have found the lines in the underlying C code which explain what is going on:

if(TYPEOF(_this) == REALSXP) {
double r = REAL(_this)[0];
if((double)((int) r) == r)
_this = coerceVector(_this, INTSXP);

This code does the following: If the vector type is REALSXP (which means numeric) then convert first member of vector to double r. Then cast r as integer and then double and if bytes are still same convert whole vector as INTSXP. Importantly, this code only checks the first element of a vector; if that element can be coerced to integer, then the whole vector is coerced, otherwise the code gives an error.

To test this hypothesis one could compile R with a custom sprintf() where double r = REAL(_this)[0]; is changed to double r = REAL(_this)[1]; and test whether c(1.5, 1) works now or not.

Thomas
  • 43,637
  • 12
  • 109
  • 140
Mikael Jumppanen
  • 2,436
  • 1
  • 17
  • 29
  • 1
    Sorry, I don't understand how this explains the differences between `c(1.5, 1)` and `c(1, 1.5)` – pomber Nov 24 '14 at 21:59
  • 1
    Excact answer can be found probably from here:[rsource/sprintf.c](https://github.com/SurajGupta/r-source/blob/46102b91b35a7befa0cf6cc6abd0da09b86f1621/src/main/sprintf.c). If someone with more experience with C could find it? – Mikael Jumppanen Nov 24 '14 at 22:22
  • Great answer, weird implementation. It must been done this way for performance reasons. – pomber Nov 26 '14 at 01:41