Home » excel » excel – Keep trailing zeroes in regex matching formula

excel – Keep trailing zeroes in regex matching formula

Posted by: admin April 23, 2020 Leave a comment

Questions:

I have a function I’ve written to handle calculations of percent reductions with significant digits, and I’m having a problem with keeping trailing zeroes.
The function:

Function RegexReduction(IValue As Double, EValue As Double) As String
    Dim TempPercent As Double
    Dim TempString As String
    Dim NumFormat As String
    Dim DecPlaces As Long
    Dim regex As Object
    Dim rxMatches As Object

    TempPercent = (1 - EValue / IValue)
    NumFormat = "0"
    Set regex = CreateObject("VBScript.RegExp")
    With regex
        .Pattern = "([^1-8])*[0-8]{1}[0-9]?"
        .Global = False
    End With
    Set rxMatches = regex.Execute(CStr(TempPercent))
    If rxMatches.Count <> 0 Then
        TempString = rxMatches.Item(0)
        DecPlaces = Len(Split(TempString, ".")(1)) - 2
        If DecPlaces > 0 Then NumFormat = NumFormat & "." & String(DecPlaces, "0")
    End If
    RegexReduction = Format(TempPercent, NumFormat & "%")
End Function

This trims percentages to two digits after any leading zeroes or nines:

99.999954165%  ->  99.99954%
34.564968%     ->  35%
0.000516%      ->  0.00052%

The one problem I’ve found isn’t related to the regex, but to Excel’s rounding:

99.50%          ->  99.5%

Is there a solution that will save trailing zeroes that could be implemented here?

How to&Answers:

I suggest a version of your function that uses LTrim in combination with Replace instead of the (costly) regular expression to calculate the value of DecPlaces. The calculation of DecPlaces has become a “one-liner”.

The rest of the code is the same except for the additional call to CDec to avoid CStr from returning a scientific notation (like 1.123642E-12) when the value is tiny.

Function Reduction(IValue As Double, EValue As Double) As String
    Dim TempPercent As Double
    Dim TempString As String
    Dim NumFormat As String
    Dim DecPlaces As Long

    TempPercent = (1 - EValue / IValue)
     ' Apply CDec so tiny numbers do not get scientific notation
    TempString = CStr(CDec(TempPercent))
    ' Count number of significant digits present by trimming away all other chars,
    ' and subtract from total length to get number of decimals to display
    DecPlaces = Len(TempString) - 2 - _
        Len(LTrim(Replace(Replace(Replace(TempString, "0"," "), "9"," "), "."," ")))
    ' Prepare format of decimals, if any
    If DecPlaces > 0 Then NumFormat = "." & String(DecPlaces, "0")
    ' Apply format
    Reduction = Format(TempPercent, "0" & NumFormat & "%")
End Function

It is assumed that TempPercent evaluates to a value between 0 and 1.

Comments on your code

You wrote:

The one problem I’ve found isn’t related to the regex, but to Excel’s rounding:

99.50%          ->  99.5%

This is actually not related to Excel’s rounding. In your code the following

DecPlaces = Len(Split(TempString, ".")(1)) - 2

will evaluate to Len(Split("0.995", ".")(1)) - 2, which is 1, and so the format you apply is 0.0%, explaining the output you get.

Also realise that although you have a capturing group in your regular expression, you do not actually use it. rxMatches.Item(0) will give you the complete matched string, not only the match with the capture group.

You apply a number format of 0% for the case the regular expression does not yield a match. Any number that has no other digits than 0 and 9 will not match. For instance 0.099 should be displayed with format 0.000% to give 9.900, but the format used is 0% as you have no Else block treating this case.

Finally, CStr can turn numbers into scientific notation, which will give wrong results as well. It seems with CDec this can be avoided.

Answer:

Here is a UDF that attempts to ‘read’ the incoming raw (non-percentage) value in order to determine the number of decimal places to include.

Function udf_Specific_Scope(rng As Range)
    Dim i As Long, str As String

    str = rng.Value2    'raw value is 0.999506 for 99.9506%

    For i = 1 To Len(str) - 1
        If Asc(Mid(str, i, 1)) <> 48 And _
           Asc(Mid(str, i, 1)) <> 57 And _
           Asc(Mid(str, i, 1)) <> 46 Then _
            Exit For
    Next i

    If InStr(1, str, Chr(46)) < i - 1 Then
        udf_Specific_Scope = Val(Format(rng.Value2 * 100, "0." & String(i - 3, Chr(48)))) & Chr(37)
    Else
        udf_Specific_Scope = Format(rng.Value2, "0%")
    End If

End Function

    specific_scope

The disadvantage here is removing the numerical value from the cell entry but that mirrors your original RegEx method. Ideally, something like the above could be written as a sub based on the Application.Selection property. Just highlight (aka Select) some cells, run the sub and it assigns a cell number format with the correct number of decimals to each in the selection.