BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
dxiao2017
Lapis Lazuli | Level 10

Hi, I have a macro like this, it runs well, but the only thing I feel not so satisfying is, I intended to round the qzscore to .01, but with the original and the second trying failed I think it will be quicker to get an answer here. I know the solution might be fairly easy, but I am just not so familiar with the rules of macro functions and computing macro values, especially ones like this, the value of the qzscore is computed through a macro value, should I use base function round(), or macro function %sysevalf, or %put or something else? Thanks a lot!

 

And I have two more questions- I happened to write the labscore statement and %put time used: statement like these and happened had the right results, but I in fact have questions about them: 1) in the %put time used: statement, for the cat() function, I did not put quotation marks around the character m, but the result is correct as what I desired(time used: 112m), why is that? Should there always be quotation marks around a character or character string inside a cat() function?

 

2) in the labscore statement, I did not use macro function to evaluate (,here, round() the value to .01) macro values (&lab1, &lab2, etc), why is that? Should I always use macro function (instead of base function) to compute or evaluate macro values?

 

The macro and resolved results are as follows, the parts with questions are marked bold in pink:

 

%macro mktscore(timetot=,timeremain=,
                lab1=,lab2=,lab3=,lab4=,lab5=,
                lab6=,lab7=,lab8=,lab9=,lab10=,
                lab11=,lab12=,lab13=,lab14=,
                lab15=,lab16=,lab17=,
                qztot=,qzincorrect=);
                
%let time=&timetot-&timeremain;
%put time used: %sysfunc(cat(%sysevalf(&time),m));

data _null_;
   labscore=round(mean(&lab1,&lab2,&lab3,
                       &lab4,&lab5,&lab6,
                       &lab7,&lab8,&lab9,
                       &lab10,&lab11,&lab12,
                       &lab13,&lab14,&lab15,
                       &lab16,&lab17),.01);
   qzcorrect=%sysevalf(&qztot-&qzincorrect);
   qzscore=qzcorrect/&qztot*100;
   one3rdtot=round(2/3*labscore+1/3*qzscore,.01);
   put labscore= qzcorrect= qzscore= one3rdtot=;
run;
%mend mktscore;
69         %mktscore(timetot=120,timeremain=8,
 70                   lab1=70,lab2=100,lab3=96,
 71                   lab4=0,lab5=0,lab6=90,
 72                   lab7=100,lab8=90,lab9=80,
 73                   lab10=100,lab11=90,lab12=90,
 74                   lab13=90,lab14=96,lab15=93,
 75                   lab16=96,lab17=100,
 76                   qztot=24,qzincorrect=7);
 time used: 112m
 
 labscore=81.24 qzcorrect=17 qzscore=70.833333333 one3rdtot=77.77 

 

1 ACCEPTED SOLUTION

Accepted Solutions
russt_sas
SAS Employee

You can use the ROUND function on the QZSCORE variable to get the rounding, for example: qzscore=round(qzcorrect/&qztot*100,.01);

 

You also asked 'Should there always be quotation marks around a character or character string inside a cat() function?'  The answer is when dealing with macro functions, they treat the value as character so no quotes are needed unlike DATA step functions need quotes to determine something is a character.

 

You also asked: 'Should I always use macro function (instead of base function) to compute or evaluate macro values?'  The answer is you need macro functions when dealing with macro variables inside the macro facility.  If you are in a DATA step creating a dataset variable, then the macro functions are not needed.  For example, you have this:

qzcorrect=%sysevalf(&qztot-&qzincorrect);

But since you are creating a SAS dateset variable the %SYSEVALF function is not needed, you could just say:

qzcorrect=&qztot-&qzincorrect;

But if you were creating a macro variable, for example with a %LET you would need %SYSEVALF, like this:

%let qzcorrect=%sysevalf(&qztot-&qzincorrect);

I hope you find this helpful.

View solution in original post

8 REPLIES 8
russt_sas
SAS Employee

You can use the ROUND function on the QZSCORE variable to get the rounding, for example: qzscore=round(qzcorrect/&qztot*100,.01);

 

You also asked 'Should there always be quotation marks around a character or character string inside a cat() function?'  The answer is when dealing with macro functions, they treat the value as character so no quotes are needed unlike DATA step functions need quotes to determine something is a character.

 

You also asked: 'Should I always use macro function (instead of base function) to compute or evaluate macro values?'  The answer is you need macro functions when dealing with macro variables inside the macro facility.  If you are in a DATA step creating a dataset variable, then the macro functions are not needed.  For example, you have this:

qzcorrect=%sysevalf(&qztot-&qzincorrect);

But since you are creating a SAS dateset variable the %SYSEVALF function is not needed, you could just say:

qzcorrect=&qztot-&qzincorrect;

But if you were creating a macro variable, for example with a %LET you would need %SYSEVALF, like this:

%let qzcorrect=%sysevalf(&qztot-&qzincorrect);

I hope you find this helpful.

dxiao2017
Lapis Lazuli | Level 10

Hi @russt_sas , thanks a lot for the answer, which accurately and perfectly addressed all of my questions!

 

What I get now are: 1) when creating variables within a data step, macro functions (such as %sysevalf) are not needed, regardless of whether the computation involves macro values or not, and

 

2) when use cat() as macro function, it treats the values inside the bracket as character strings so that no quotation marks are needed for character values.

 

I modified my macro base on both yours and @Tom 's suggestion, now it looks much better and works very good, thanks a lot @russt_sas, very helpful!

 

%macro mktscore(timetot=,timeremain=,
                lab1=,lab2=,lab3=,lab4=,lab5=,
                lab6=,lab7=,lab8=,lab9=,lab10=,
                lab11=,lab12=,lab13=,lab14=,
                lab15=,lab16=,lab17=,
                qztot=,qzincorrect=);
                
%let time=%sysevalf(&timetot-&timeremain);
%put time used: &time.m;

data _null_;
   labscore=round(mean(&lab1,&lab2,&lab3,
                       &lab4,&lab5,&lab6,
                       &lab7,&lab8,&lab9,
                       &lab10,&lab11,&lab12,
                       &lab13,&lab14,&lab15,
                       &lab16,&lab17),.01);
   qzcorrect=&qztot-&qzincorrect;
   qzscore=round(qzcorrect/&qztot*100,.01);
   one3rdtot=round(2/3*labscore+1/3*qzscore,.01);
   put labscore= qzcorrect= qzscore= one3rdtot=;
run;
%mend mktscore;
69         %mktscore(timetot=120,timeremain=8,
 70                   lab1=70,lab2=100,lab3=96,
 71                   lab4=0,lab5=0,lab6=90,
 72                   lab7=100,lab8=90,lab9=80,
 73                   lab10=100,lab11=90,lab12=90,
 74                   lab13=90,lab14=96,lab15=93,
 75                   lab16=96,lab17=100,
 76                   qztot=24,qzincorrect=7);
 time used: 112m
 
 labscore=81.24 qzcorrect=17 qzscore=70.83 one3rdtot=77.77

 

Tom
Super User Tom
Super User

Do NOT use the CAT series of functions with %SYSFUNC().

 

First of all you don't need them.  To concatenate text in macro code just write the text next to each other.  Remember to use a period to mark the end of the macro variable name so the macro processor does not look for the TIMEM macro variable instead of the TIME macro variable.

%let time=%sysevalf(&timetot-&timeremain);
%put time used: &time.m;

Second because the CAT series of functions can take both numeric and character arguments it drives the %SYSFUNC() macro function crazy trying to decide how to treat the text you have given it.    What should it do with a string like 1E2 ?  Is that supposed to be the number 100? 

 

If you have a need for the CATX() function in macro logic you could use this macro instead of trying to get it to work with %sysfunc(). 

 https://github.com/sasutils/macros/blob/master/catx.sas

The CAT... series of functions do not work well with %SYSFUNC() because they
can accept either numeric or character values.  So %SYSFUNC() has to try
and figure out if the value you passed is a number or a string. Which can
cause unwanted messages in the LOG and worse.

Example issue with %sysfunc(catx()):

    1    %put |%sysfunc(catx(^,a,,c))|;
    ERROR: %SYSEVALF function has no expression to evaluate.
    |a^c|

 

dxiao2017
Lapis Lazuli | Level 10

Hi @Tom , I think what you mentioned is a very brilliant point! It helps improve the way I using %let and %put statements to create macro and display the macro values.

 

What I get now are: 1) when use %let statement to compute and generate macros, it's good practice to finish the computation within the %let statement (and do not leave it to the %put statement), especially when the computation involves macro values and need %sysfunc() or %sysevalf() functions, and

 

2) when use the %put statement to display macro values, NO cat() function is needed (here previously when I  wrote that statement I do not know what was going on in my mind, I actually knew if I want to display a macro value just type it and it's ok, but the scenario was a bit complicate, i.e., involves computing macro values so I forgot the simple and normal way of displaying a macro value through %put statement), and

 

3) as you said, do NOT use cat() series functions with %sysfunc() because SAS will have to decide whether the arguments' text are numeric or character.

 

Thanks a lot for all the information you give, @Tom , very helpful (I guess I cannot access github btw).

Tom
Super User Tom
Super User

In general it is best to think about what SAS code you want the macro to generate (instead of trying to think of clever ways to program in the macro language itself).

 

Since in this case you are generating a data step anyway you can just use SAS for all of your calculations.  Which means you can just use a FORMAT to do the rounding of the values you write to the LOG.

 

Note that when you need to pass a varying amount of values to a macro just use a space delimited list.

%macro mktscore
(timetot=
,timeremain=
,labscores=
,qztot=
,qzincorrect=
);
                
data _null_;
  array labscores (%sysfunc(countw(&labscores,%str( )))) _temporary_ (&labscores) ;
  time=&timetot - &timeremain;
  labscore=mean(of labscores[*]);
  qzcorrect=&qztot-&qzincorrect;
  qzscore=100*qzcorrect/&qztot;
  one3rdtot=2/3*labscore+1/3*qzscore;
  put 'time used: ' time :comma32.2 +(-1) 'm' ;
  put (labscore qzcorrect qzscore one3rdtot) (= :32.2);
run;
%mend mktscore;

%mktscore
(timetot=120
,timeremain=8
,labscores=70 100 96 0 0 90 100 90 80 100 90 90 90 96 93 96 100
,qztot=24
,qzincorrect=7
);

Results

 93         options mprint;
 94         %mktscore
 95         (timetot=120
 96         ,timeremain=8
 97         ,labscores=70 100 96 0 0 90 100 90 80 100 90 90 90 96 93 96 100
 98         ,qztot=24
 99         ,qzincorrect=7
 100        );
 MPRINT(MKTSCORE):   data _null_;
 MPRINT(MKTSCORE):   array labscores (17) _temporary_ (70 100 96 0 0 90 100 90 80 100 90 90 90 96 93 96 100) ;
 MPRINT(MKTSCORE):   time=120 - 8;
 MPRINT(MKTSCORE):   labscore=mean(of labscores[*]);
 MPRINT(MKTSCORE):   qzcorrect=24-7;
 MPRINT(MKTSCORE):   qzscore=100*qzcorrect/24;
 MPRINT(MKTSCORE):   one3rdtot=2/3*labscore+1/3*qzscore;
 MPRINT(MKTSCORE):   put 'time used: ' time :comma32.2 +(-1) 'm' ;
 MPRINT(MKTSCORE):   put (labscore qzcorrect qzscore one3rdtot) (= :32.2);
 MPRINT(MKTSCORE):   run;
 
 time used: 112.00m
 labscore=81.24 qzcorrect=17.00 qzscore=70.83 one3rdtot=77.77
 NOTE: DATA statement used (Total process time):
       real time           0.00 seconds
       cpu time            0.00 seconds

 

 

PaigeMiller
Diamond | Level 26

@Tom wrote:

 

Since in this case you are generating a data step anyway you can just use SAS for all of your calculations.  Which means you can just use a FORMAT to do the rounding of the values you write to the LOG.

 


This is an excellent point from @Tom . Rounding using a FORMAT is often superior to rounding using the ROUND function. Why? Because if these are intermediate calculations that will be used later in the program in a further analysis, rounding using the ROUND function can lead to incorrect answers in later steps that use that variable. Using the FORMAT does not truncate the value and so later steps still can use the entire collection of digits in the value, leading to correct answers. 

 

Of course, I feel that too many people focus so tightly on the coding problem that they have a problem with, and don't explain how the answers will be used. This is a sub-optimal method, and can lead to sub-optimal answers. @dxiao2017 : We need to know the big picture, not just the coding problem you have. If we know the big picture, we won't give you answers that don't work in the big picture. If we know the big picture, we can also advise on better approaches and logic (which can be easier to program and a better method) to get you to your final result. This is called the XY-problem. Please read about the XY-problem (here and here).

 

Side issue: there are times when ROUNDing is the right thing to do, but without knowing the big picture, we can't even begin to advise you on this.

--
Paige Miller
dxiao2017
Lapis Lazuli | Level 10

Hi @PaigeMiller , thanks a lot for your suggestion, I think you mentioned a very good point, that is, when I want to store some raw data in a dataset for future use and meanwhile, at the moment I also want to display it in certain format (e.g., rounding), using format is better than use round() function.

 

As for the big picture (of the macro), I create it just for my own use, that is, to calculate the score (also record the total time I used, in minutes) for my practice of SAS techniques, both labs and quizzes, to record a score (range from 1 to 100) for each lab (and calculate the mean score, which is rounded to .01) and the number of quizzes I get correct (which is a integer) out of total number of quizzes(, as the quiz score, which is a fraction displayed as a percentage), and calculate the overall score base on the lab score and quiz score. I think @Tom 's version is more advanced (and good) and my own version is easier (for myself). It's sufficient for my own use at the moment.

 

Thanks a lot for your suggestion, I appreciate, @PaigeMiller ! (have to say that the above description and explanation consume much more energy and time than I wrote that macro, I thought it was clear and simple enough as plain text and no need any explanation)

dxiao2017
Lapis Lazuli | Level 10

Hi @Tom , thanks a lot for offering your version of the macro, helps me learn further! Two advanced techniques I can learn from your version are:

 

1) use array without a do loop (this is a technique I never used before, will practice it later), and the number of elements can be defined through functions and calculations (aside from use a number or asterisk *), and

 

2) the way to apply format to the values to be displayed through a put statement within a data step (that's handy technique I may often use), I practiced a bit as follows:

69         data _null_;
 70            date='24mar2026'd;
 71            score1=12.3344;
 72            score2=0.67809;
 73            score3=109.998;
 74            put date :mmddyy10. score1 :5.2;
 75            put (score2 score3) (= :5.1);
 76         run;
 
 03/24/2026 12.33
 score2=0.7 score3=110.0

Thanks very much @Tom !

 

Catch up on SAS Innovate 2026

Nearly 200 sessions are now available on demand with the SAS Innovate Digital Pass.

Explore Now →
How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 8 replies
  • 759 views
  • 4 likes
  • 4 in conversation