From 9887efcf60839399bec1e8c9389a3f7cb79e5d93 Mon Sep 17 00:00:00 2001 From: Allan Date: Mon, 19 Jun 2023 23:54:18 +0100 Subject: [PATCH] feat: new mp_aligndecimal macro Includes a test, and an update to the mp_assertcolvals test to include a new test type (NOVAL) --- base/mp_aligndecimal.sas | 94 +++++++++++++++++++++++++++++ base/mp_assertcolvals.sas | 6 +- tests/base/mp_aligndecimal.test.sas | 44 ++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 base/mp_aligndecimal.sas create mode 100644 tests/base/mp_aligndecimal.test.sas diff --git a/base/mp_aligndecimal.sas b/base/mp_aligndecimal.sas new file mode 100644 index 0000000..35ef8e2 --- /dev/null +++ b/base/mp_aligndecimal.sas @@ -0,0 +1,94 @@ +/** + @file + @brief Apply leading blanks to align numbers vertically in a char variable + @details This is particularly useful when storing numbers (as character) that + need to be sorted. + + It works by splitting the number left and right of the decimal place, and + aligning it accordingly. A temporary variable is created as part of this + process (which is automatically dropped) + + The macro can be used only in data step, eg as follows: + + data _null_; + length myvar $50; + do i=1 to 1000 by 50; + if mod(i,2)=0 then j=ranuni(0)*i*100; + else j=i*100; + + %mp_aligndecimal(myvar,width=7) + + leading_spaces=length(myvar)-length(cats(myvar)); + putlog +leading_spaces myvar; + end; + run; + + The generated code will look something like this: + + length aligndp4e49996 $7; + if index(myvar,'.') then do; + aligndp4e49996=cats(scan(myvar,1,'.')); + aligndp4e49996=right(aligndp4e49996); + myvar=aligndp4e49996!!'.'!!cats(scan(myvar,2,'.')); + end; + else do; + aligndp4e49996=myvar; + aligndp4e49996=right(aligndp4e49996); + myvar=aligndp4e49996; + end; + + Results (myvar variable): + + 0.7683559324 + 122.8232796 + 99419.50552 + 42938.5143414 + 763.3799189 + 15170.606073 + 15083.285773 + 85443.198707 + 2022999.2251 + 12038.658867 + 1350582.6734 + 52777.258221 + 11723.347628 + 33101.268376 + 6181622.8603 + 7390614.0669 + 73384.537893 + 1788362.1016 + 2774586.2219 + 7998580.8415 + + + @param var The (data step) variable to create + @param width= (8) The number of characters BEFORE the decimal point + +

SAS Macros

+ @li mf_getuniquename.sas + +

Related Programs

+ @li mp_aligndecimal.test.sas + + @version 9.2 + @author Allan Bowe +**/ + +%macro mp_aligndecimal(var,width=8); + + %local tmpvar; + %let tmpvar=%mf_getuniquename(prefix=aligndp); + length &tmpvar $&width; + if index(&var,'.') then do; + &tmpvar=cats(scan(&var,1,'.')); + &tmpvar=right(&tmpvar); + &var=&tmpvar!!'.'!!cats(scan(&var,2,'.')); + end; + else do; + &tmpvar=cats(&var); + &tmpvar=right(&tmpvar); + &var=&tmpvar; + end; + drop &tmpvar; + +%mend mp_aligndecimal; diff --git a/base/mp_assertcolvals.sas b/base/mp_assertcolvals.sas index 7176782..129c39d 100644 --- a/base/mp_assertcolvals.sas +++ b/base/mp_assertcolvals.sas @@ -42,6 +42,7 @@ @param [in] test= (ALLVALS) The test to apply. Valid values are: @li ALLVALS - Test is a PASS if ALL values have a match in checkvals @li ANYVAL - Test is a PASS if at least 1 value has a match in checkvals + @li NOVAL - Test is a PASS if there are NO matches in checkvals @param [out] outds= (work.test_results) The output dataset to contain the results. If it does not exist, it will be created, with the following format: |TEST_DESCRIPTION:$256|TEST_RESULT:$4|TEST_COMMENTS:$256| @@ -97,7 +98,7 @@ %let test=%upcase(&test); - %if &test ne ALLVALS and &test ne ANYVAL %then %do; + %if &test ne ALLVALS and &test ne ANYVAL and &test ne NOVAL %then %do; %mp_abort( mac=&sysmacroname, msg=%str(Invalid test - &test) @@ -153,6 +154,9 @@ %else %if &test=ALLVALS %then %do; if &result=0 then test_result='PASS'; %end; + %else %if &test=NOVAL %then %do; + if &result=&orig then test_result='PASS'; + %end; %else %do; test_comments="&sysmacroname: Unsatisfied test condition - &test"; %end; diff --git a/tests/base/mp_aligndecimal.test.sas b/tests/base/mp_aligndecimal.test.sas new file mode 100644 index 0000000..e8e4b06 --- /dev/null +++ b/tests/base/mp_aligndecimal.test.sas @@ -0,0 +1,44 @@ +/** + @file + @brief Testing mp_aligndecimal macro + @details Creates an aligned variable and checks the number of leading blanks + +

SAS Macros

+ @li mp_aligndecimal.sas + @li mp_assertcolvals.sas + +**/ + + + +/* target values */ +data work.checkds; + do checkval='1234.56',' 123.45',' 123.4 ',' 1.2 ',' 0'; + output; + end; +run; + +/* raw values */ +data work.rawds; + set work.checkds; + tgtvar=cats(checkval); + drop checkval; +run; + +%mp_assertcolvals(work.rawds.tgtvar, + checkvals=work.checkds.checkval, + desc=No values match (ready to align), + test=NOVAL +) + +/* aligned values */ +data work.finalds; + set work.rawds; + %mp_aligndecimal(tgtvar,width=4) +run; + +%mp_assertcolvals(work.finalds.tgtvar, + checkvals=work.checkds.checkval, + desc=All values match (aligned), + test=ALLVALS +)