Search code examples
crprintfcoercion

sprintf invalid format '%d'


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?


Solution

  • 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.