Round by the power of two

Gustav BrockMVP
CERTIFIED EXPERT
Published:
Updated:
Edited by: Andrew Leniart
Usually, rounding is performed by some power of 10 - to thousands, hundreds, tens, or integer - or to one, two, or more decimals. But rounding can also be done to a power of two, say, 16 or 64, or 1/32 or 1/1024, even for extreme values.

Unusual rounding

It may not be every day you are required to round a number by a power of two. But when the task shows up, here is how to get it done, also for non-trivial numbers like very large numbers or numbers with high precision (many decimals).


The power of two

In case you are not familiar with this, the power of two means 2n. For example, 2equals 16. Other well-known values are listed here with in the top row:


If is positive, we get numbers like:

0
1
2
3
4
5
6
7
8
9
10
1
2
4
8
16
32
64
128
256
512
1024


For example, 18 or 14 rounded by 8 results in 16.


If is negative, we get numbers like:

0
1
2
3
4
5
6
7
8
9
10
1
1/2
1/4
1/8
1/16
1/32
1/64
1/128
1/256
1/512
1/1024


For example, 1.7 or 1.8 rounded by 1/4 results in 1.75


Large values and high precision

Rounding small numbers with few decimals is not a big deal. 

However, the goal is to be able to round both very large numbers and numbers with many decimals. To achieve this, data type  Decimal  is used because of its much higher precision than Double.


The smallest fraction for rounding is: 

2-21 which equals 1/2097152 or 0.000000476837158203125


The largest numerical value to round with maximum resolution is:

79228162 + (221 - 8) / 221 which equals 79228162.999996185302734375


With lesser precision (fewer decimals) much larger values can be handled. 

However, the expected rounded value must not exceed the range of:

± 79,228,162,514,264,337,593,543,950,335 which is close to ± 296


The function

Let's have a look at the main function that rounds by 4/5


' Rounds Value by 4/5 to the power of two as specified with parameter Exponent.
'
' If Exponent is positive, the fraction of Value is rounded to an integer a fraction of 1 / 2 ^ Exponent.
' If Exponent is zero, Value is rounded to an integer.
' If Exponent is negative, Value is rounded to an integer and a multiplum of 2 ^ Exponent.
'
' Rounds correctly Value until max/min value limited by a scaling of 2 raised to the power of Exponent.
'
' Smallest fraction for rounding is:
'   2 ^ -21 (= 1 / 2097152)
' or:
'   0.000000476837158203125
'
' Largest numerical value to round with maximum resolution is:
'   79228162 + (2 ^ 21 - 8) / 2 ^ 21
' or:
'   79228162.999996185302734375
'
' Expected rounded value must not exceed the range of:
'   +/- 79,228,162,514,264,337,593,543,950,335
'
' Uses CDec() to prevent bit errors of reals.
'
' Execution time is about 1µs.
'
' Examples, integers:
'   RoundMidBase2(1001, -3)             -> 1000
'   RoundMidBase2(1001, -8)             -> 1024
'   RoundMidBase2(17.03, -4)            ->   16
'   RoundMidBase2(17.03, -5)            ->   32
'
' Examples, decimals:
'   1 / 2 ^ 4 = 0.0625                              Step value when rounding by 1/16
'   RoundMidBase2(17.03, 4)             -> 17.0     Rounding down
'   RoundMidBase2(17.08, 4)             -> 17.0625  Rounding down
'   RoundMidBase2(17.1, 4)              -> 17.125   Rounding up
'   RoundMidBase2(17.2, 4)              -> 17.1875  Rounding down
'
'   1 / 2 ^ 5 = 0.03125                             Step value when rounding by 1/32
'   RoundMidBase2(17.125 + 0.00000, 4)  -> 17.125   Exact value. No rounding.
'   RoundMidBase2(17.125 + 0.03124, 4)  -> 17.125   Rounding down
'   RoundMidBase2(17.125 + 0.03125, 4)  -> 17.1875  Rounding up
'
' More info on the power of two and rounding:
'   https://en.wikipedia.org/wiki/Power_of_two
'
' 2018-04-01. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function RoundMidBase2( _
    ByVal Value As Variant, _
    Optional ByVal Exponent As Long) _
    As Variant

    Dim Scaling     As Variant
    Dim Half        As Variant
    Dim ScaledValue As Variant
    Dim ReturnValue As Variant
   
    ' Only round if Value is numeric and ReturnValue can be different from zero.
    If Not IsNumeric(Value) Then
        ' Nothing to do.
        ReturnValue = Null
    ElseIf Value = 0 Then
        ' Nothing to round.
        ' Return Value as is.
        ReturnValue = Value
    Else
        Scaling = CDec(Base2 ^ Exponent)
       
        If Scaling = 0 Then
            ' A very large value for Exponent has minimized scaling.
            ' Return Value as is.
            ReturnValue = Value
        Else
            ' Standard 4/5 rounding.
            ' Very large values for Exponent can cause an out-of-range error when dividing.
            On Error Resume Next
            Half = CDec(0.5)
            If Value > 0 Then
                ScaledValue = Int(CDec(Value) * Scaling + Half)
            Else
                ScaledValue = -Int(-CDec(Value) * Scaling + Half)
            End If
            ReturnValue = ScaledValue / Scaling
            If Err.Number <> 0 Then
                ' Decimal overflow.
                ' Round Value without conversion to Decimal.
                Half = CDbl(0.5)
                If Value > 0 Then
                    ScaledValue = Int(Value * Scaling + Half)
                Else
                    ScaledValue = -Int(-Value * Scaling + Half)
                End If
                ReturnValue = ScaledValue / Scaling
            End If
        End If
        If Err.Number <> 0 Then
            ' Rounding failed because values are near one of the boundaries of type Double.
            ' Return value as is.
            ReturnValue = Value
        End If
    End If
   
    RoundMidBase2 = ReturnValue

End Function

The core is the scaling:


Scaling = CDec(Base2 ^ Exponent)

where Base2 ^ Exponent is the power of two to use.


The usage is quite simple, for example for integer rounding:


RoundedValue = RoundMidBase2(17.03, -4)
' RoundedValue -> 16


and for rounding of fractions:


RoundedValue = RoundMidBase2(17.1, 4)
' RoundedValue -> 17.125


Note, that for parameter Exponent, a positive value will round to fractions, while a negative will round to integer. This is to keep the syntax identical to that of function RoundMid, where Exponent indicates the count of decimals.


Alternative rounding - up or down

In some cases, rounding by 4/5 is not suitable. Depending on the scenario, it might be more feasible to round either up or down, thus two supplemental functions are included. They are built much similar to RoundMidBase2 and the sister functions, RoundUp and RoundDown.


To round up, use the function RoundUpBase2 


' Rounds Value up to the power of two as specified with parameter Exponent.
'
' If Exponent is positive, the fraction of Value is rounded to an integer a fraction of 1 / 2 ^ Exponent.
' If Exponent is zero, Value is rounded to an integer.
' If Exponent is negative, Value is rounded to an integer and a multiplum of 2 ^ Exponent.
'
' Optionally, rounds negative values away from zero.
'
' Rounds correctly Value until max/min value limited by a scaling of 2 raised to the power of Exponent.
'
' Smallest fraction for rounding is:
'   2 ^ -21 (= 1 / 2097152)
' or:
'   0.000000476837158203125
'
' Largest numerical value to round with maximum resolution is:
'   79228162 + (2 ^ 21 - 8) / 2 ^ 21
' or:
'   79228162.999996185302734375
'
' Expected rounded value must not exceed the range of:
'   +/- 79,228,162,514,264,337,593,543,950,335
'
' Uses CDec() to prevent bit errors of reals.
'
' Execution time is about 0.5µs for rounding to integer, else about 1µs.
'
' Examples, integers:
'   RoundUpBase2(1001, -3)              -> 1008
'   RoundUpBase2(1001, -8)              -> 1024
'   RoundUpBase2(17.03, -4)             ->   32
'   RoundUpBase2(17.03, -5)             ->   32
'
' Examples, decimals:
'   1 / 2 ^ 4 = 0.0625                              Step value when rounding by 1/16
'   RoundUpBase2(17.03, 4)              -> 17.0625
'   RoundUpBase2(17.08, 4)              -> 17.125
'   RoundUpBase2(17.1, 4)               -> 17.125
'   RoundUpBase2(17.2, 4)               -> 17.25
'
'   1 / 2 ^ 5 = 0.03125                             Step value when rounding by 1/32
'   RoundUpBase2(17.125 + 0.00000, 4)   -> 17.125   Exact value. No rounding.
'   RoundUpBase2(17.125 + 0.03124, 4)   -> 17.1875
'   RoundUpBase2(17.125 + 0.03125, 4)   -> 17.1875
'
' More info on the power of two and rounding:
'   https://en.wikipedia.org/wiki/Power_of_two
'
' 2018-04-02. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function RoundUpBase2( _
    ByVal Value As Variant, _
    Optional ByVal Exponent As Long, _
    Optional ByVal RoundingAwayFromZero As Boolean) _
    As Variant
    Dim Scaling     As Variant
    Dim ScaledValue As Variant
    Dim ReturnValue As Variant
   
    ' Only round if Value is numeric and ReturnValue can be different from zero.
    If Not IsNumeric(Value) Then
        ' Nothing to do.
        ReturnValue = Null
    ElseIf Value = 0 Then
        ' Nothing to round.
        ' Return Value as is.
        ReturnValue = Value
    Else
        If Exponent <> 0 Then
            Scaling = CDec(Base2 ^ Exponent)
        Else
            Scaling = 1
        End If
        If Scaling = 0 Then
            ' A very large value for Exponent has minimized scaling.
            ' Return Value as is.
            ReturnValue = Value
        ElseIf RoundingAwayFromZero = False Or Value > 0 Then
            ' Round numeric value up.
            If Scaling = 1 Then
                ' Integer rounding.
                ReturnValue = -Int(-Value)
            Else
                ' First try with conversion to Decimal to avoid bit errors for some reals like 32.675.
                On Error Resume Next
                ScaledValue = -Int(CDec(-Value) * Scaling)
                ReturnValue = ScaledValue / Scaling
                If Err.Number <> 0 Then
                    ' Decimal overflow.
                    ' Round Value without conversion to Decimal.
                    ScaledValue = -Int(-Value * Scaling)
                    ReturnValue = ScaledValue / Scaling
                End If
            End If
        Else
            ' Round absolute value up.
            If Scaling = 1 Then
                ' Integer rounding.
                ReturnValue = Int(Value)
            Else
                ' First try with conversion to Decimal to avoid bit errors for some reals like 32.675.
                On Error Resume Next
                ScaledValue = Int(CDec(Value) * Scaling)
                ReturnValue = ScaledValue / Scaling
                If Err.Number <> 0 Then
                    ' Decimal overflow.
                    ' Round Value without conversion to Decimal.
                    ScaledValue = Int(Value * Scaling)
                    ReturnValue = ScaledValue / Scaling
                End If
            End If
        End If
        If Err.Number <> 0 Then
            ' Rounding failed because values are near one of the boundaries of type Double.
            ' Return value as is.
            ReturnValue = Value
        End If
    End If
   
    RoundUpBase2 = ReturnValue
End Function

and to round down, use the function RoundDownBase2 


' Rounds Value down to the power of two as specified with parameter Exponent.
'
' If Exponent is positive, the fraction of Value is rounded to an integer a fraction of 1 / 2 ^ Exponent.
' If Exponent is zero, Value is rounded to an integer.
' If Exponent is negative, Value is rounded to an integer and a multiplum of 2 ^ Exponent.
'
' Optionally, rounds negative values towards zero.
'
' Rounds correctly Value until max/min value limited by a scaling of 2 raised to the power of Exponent.
'
' Smallest fraction for rounding is:
'   2 ^ -21 (= 1 / 2097152)
' or:
'   0.000000476837158203125
'
' Largest numerical value to round with maximum resolution is:
'   79228162 + (2 ^ 21 - 8) / 2 ^ 21
' or:
'   79228162.999996185302734375
'
' Expected rounded value must not exceed the range of:
'   +/- 79,228,162,514,264,337,593,543,950,335
'
' Uses CDec() to prevent bit errors of reals.
'
' Execution time is about 0.5µs for rounding to integer, else about 1µs.
'
' Examples, integers:
'   RoundDownBase2(1001, -3)                -> 1000
'   RoundDownBase2(1001, -8)                ->  768
'   RoundDownBase2(17.03, -4)               ->   16
'   RoundDownBase2(17.03, -5)               ->    0
'
' Examples, decimals:
'   1 / 2 ^ 4 = 0.0625                                  Step value when rounding by 1/16
'   RoundDownBase2(17.03, 4)                -> 17
'   RoundDownBase2(17.08, 4)                -> 17.0625
'   RoundDownBase2(17.1, 4)                 -> 17.0625
'   RoundDownBase2(17.2, 4)                 -> 17.1875
'
'   1 / 2 ^ 5 = 0.03125                                 Step value when rounding by 1/32
'   RoundDownBase2(17.125 + 0.00000, 4)     -> 17.125   Exact value. No rounding.
'   RoundDownBase2(17.125 + 0.03124, 4)     -> 17.125
'   RoundDownBase2(17.125 + 0.03125, 4)     -> 17.125
'
' More info on the power of two and rounding:
'   https://en.wikipedia.org/wiki/Power_of_two
'
' 2018-04-02. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function RoundDownBase2( _
    ByVal Value As Variant, _
    Optional ByVal Exponent As Long, _
    Optional ByVal RoundingToZero As Boolean) _
    As Variant
   
    Dim Scaling     As Variant
    Dim ScaledValue As Variant
    Dim ReturnValue As Variant
   
    ' Only round if Value is numeric and ReturnValue can be different from zero.
    If Not IsNumeric(Value) Then
        ' Nothing to do.
        ReturnValue = Null
    ElseIf Value = 0 Then
        ' Nothing to round.
        ' Return Value as is.
        ReturnValue = Value
    Else
        If Exponent <> 0 Then
            Scaling = CDec(Base2 ^ Exponent)
        Else
            Scaling = 1
        End If
        If Scaling = 0 Then
            ' A very large value for Exponent has minimized scaling.
            ' Return Value as is.
            ReturnValue = Value
        ElseIf RoundingToZero = False Then
            ' Round numeric value down.
            If Scaling = 1 Then
                ' Integer rounding.
                ReturnValue = Int(Value)
            Else
                ' First try with conversion to Decimal to avoid bit errors for some reals like 32.675.
                ' Very large values for Exponent can cause an out-of-range error when dividing.
                On Error Resume Next
                ScaledValue = Int(CDec(Value) * Scaling)
                ReturnValue = ScaledValue / Scaling
                If Err.Number <> 0 Then
                    ' Decimal overflow.
                    ' Round Value without conversion to Decimal.
                    ScaledValue = Int(Value * Scaling)
                    ReturnValue = ScaledValue / Scaling
                End If
            End If
        Else
            ' Round absolute value down.
            If Scaling = 1 Then
                ' Integer rounding.
                ReturnValue = Fix(Value)
            Else
                ' First try with conversion to Decimal to avoid bit errors for some reals like 32.675.
                ' Very large values for NumDigitsAfterDecimal can cause an out-of-range error when dividing.
                On Error Resume Next
                ScaledValue = Fix(CDec(Value) * Scaling)
                ReturnValue = ScaledValue / Scaling
                If Err.Number <> 0 Then
                    ' Decimal overflow.
                    ' Round Value with no conversion.
                    ScaledValue = Fix(Value * Scaling)
                    ReturnValue = ScaledValue / Scaling
                End If
            End If
        End If
        If Err.Number <> 0 Then
            ' Rounding failed because values are near one of the boundaries of type Double.
            ' Return value as is.
            ReturnValue = Value
        End If
    End If
   
    RoundDownBase2 = ReturnValue
End Function

For both functions, you will again see, that Decimal is used as widely as possible to preserve a high precision, and only for very large values - in case of an overflow - falls back to use Double.


Expose the result

Since the functions will return a rounded fraction as the nearest equivalent decimal value, it will likely be difficult to check or verify the result visually. 

For example, who can tell if this result value for RoundedValue is as expected:


RoundedValue = RoundMidBase2(17.22, 17)
' RoundedValue -> 17.220001220703125

To reveal, that this incomprehensive decimal number expresses:

17 7209/32768

another function, ConvertDecimalFractions, can be used. 


First, an Enum is created to be used when specifying the rounding method:


Public Enum rmRoundingMethod
    Down = -1
    Midpoint = 0
    Up = 1
End Enum

You'll recognize this as the data type of the fifth parameter, RoundingMethod, where it defaults to use 4/5 rounding:


' Rounds and converts a decimal value to an integer and the fraction of an integer
' using 4/5 midpoint rounding, optionally rounding up or down.
'
' Rounding method is determined by parameter RoundingMethod.
' For rounding up or down, rounding of negative values can optionally be set to
' away-from-zero or towards-zero respectively by parameter RoundingAsAbsolute.
'
' Returns the rounded value as a decimal.
' Returns numerator and denominator of the fraction by reference.
'
' For general examples, see function RoundMidBase2, RoundUpBase2, and RoundDownBase2.
'
' Will, for example, convert decimal inches to integer inches and a fraction of inches.
' However, numerator and denominator of the fraction are returned by reference in the
' parameters Numerator and Denominator for the value to be formatted as text by the
' calling procedure.
'
' Example:
'   Value = 7.22
'   Exponent = 2    ' will round to 1/4.
'   Numerator = 0
'   Denominator = 0
'
'   Result = ConvertDecimalFractions(Value, Exponent, Numerator, Denominator)
'
'   Result = 7.25
'   Numerator = 1
'   Denominator = 4
'
'   Result = ConvertDecimalFractions(Value, Exponent, Numerator, Denominator, Up)
'
'   Result = 7.25
'   Numerator = 1
'   Denominator = 4
'
'   Result = ConvertDecimalFractions(Value, Exponent, Numerator, Denominator, Down)
'
'   Result = 7
'   Numerator = 0
'   Denominator = 0
'
' If negative, parameter Exponent determines the rounding of the fraction as
' 1 / 2 ^ Exponent with a maximum of 21 - or from 1 / 2 to 1 / 2097152.
' For inches, that is a range from 12.7 mm to about 12.1 nm.
'
' If zero or positive, parameter Exponent determines the rounding of the
' integer value with 2 ^ Exponent with a maximum of 21 - or from 1 to 2097152.
' For inches, that is a range from 25.4 mm to about 53.27 km.
'
' Also, se comments for the required functions:
'
'   RoundUpBase2
'   RoundMidBase2
'   RoundDownBase2
'
' 2018-04-05. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function ConvertDecimalFractions( _
    ByVal Value As Variant, _
    ByVal Exponent As Integer, _
    Optional ByRef Numerator As Long, _
    Optional ByRef Denominator As Long, _
    Optional RoundingMethod As rmRoundingMethod = Midpoint, _
    Optional RoundingAsAbsolute As Boolean) _
    As Variant
    
    Dim Number      As Variant
    Dim Fraction    As Variant
    
    ' Validate rounding method.
    Select Case RoundingMethod
        Case Up, Midpoint, Down
            ' OK.
        Case Else
            ' Use default rounding method.
            RoundingMethod = Midpoint
    End Select
    
    If Exponent <= 0 Then
        ' Integer rounding only.
        Select Case RoundingMethod
            Case Up
                Number = RoundUpBase2(Value, Exponent, RoundingAsAbsolute)
            Case Midpoint
                Number = RoundMidBase2(Value, Exponent)
            Case Down
                Number = RoundDownBase2(Value, Exponent, RoundingAsAbsolute)
        End Select
        Fraction = 0
        Numerator = 0
        Denominator = 0
    Else
        ' Rounding with fractions.
        Number = Fix(CDec(Value))
        Select Case RoundingMethod
            Case Up
                Fraction = RoundUpBase2(Value - Number, Exponent, RoundingAsAbsolute)
            Case Midpoint
                Fraction = RoundMidBase2(Value - Number, Exponent)
            Case Down
                Fraction = RoundDownBase2(Value - Number, Exponent, RoundingAsAbsolute)
        End Select
        
        If Fraction = 0 Or Abs(Fraction) = 1 Then
            ' Fraction has been rounded to 0 or +/-1.
            Numerator = 0
            Denominator = 0
        Else
            ' Calculate numerator and denominator for the fraction.
            Denominator = Base2 ^ Exponent
            Numerator = Fraction * Denominator
            ' Find the smallest denominator.
            While Numerator Mod Base2 = 0
                Numerator = Numerator / Base2
                Denominator = Denominator / Base2
            Wend
        End If
    End If
    
    ConvertDecimalFractions = Number + Fraction
    
End Function

Using the previous functions, this function rounds and converts a decimal value to an integer and the fraction of an integer, and then returns:


  • the rounded value as a decimal number as the output
  • the numerator and denominator of the fraction as integers by reference


An example will illustrate this. Here is a test value and a set of parameters and the resulting values:


Value = 7.22
Exponent = 2    ' will round to 1/4.
Numerator = 0
Denominator = 0

Result = ConvertDecimalFractions(Value, Exponent, Numerator, Denominator)

IntegerResult = Fix(Result)

' Result        -> 7.25
' IntegerResult -> 7
' Numerator     -> 1
' Denominator   -> 4

By applying some formatting to the output values and concatenate these as a string, the result could be presented as:

7 1/4


Smallest denominator

It is not difficult to calculate "a" numerator and denominator. However, you wouldn't want 1/4 in the example above to be returned as 4/16 or 256/1024.


This is taken care of in the loop:


            ' Calculate numerator and denominator for the fraction.
            Denominator = Base2 ^ Exponent
            Numerator = Fraction * Denominator
            ' Find the smallest denominator.
            While Numerator Mod Base2 = 0
                Numerator = Numerator / Base2
                Denominator = Denominator / Base2
            Wend

The trick is to calculate the modulus 2 of the numerator. If zero, the numerator is even and can be divided by 2, which is done before the next loop. When reaching an uneven numerator, here 1, the loop exits, and we have the smallest numerator and denominator.


Debugging

Most people, even experienced programmers, have a hard time determining the Base 2 components of numbers. For small integers, they can be calculated relatively fast, but for larger values and for decimals beyond the most trivial - as 1/4 and 1/8 - it quickly gets very hard.


So, to help debugging a number, a function has been created, which extracts and lists the Base 2 components of the number, DebugBase2 


' Rounds a value and prints the result in its Base 2 components
' and as a decimal value, and in a integer-fraction format.
'
' Will accept values within +/- 2 ^ 96.
' Also, se comments for the required functions:
'
'   ConvertDecimalFractions
'   RoundMidBase2
'   Log2
'
' Example:
'
'   DebugBase2 746.873, 2
'   Exponent      2 ^ Exponent  Factor        Value         Fraction
'    9             512           1             512
'    8             256           0             0
'    7             128           1             128
'    6             64            1             64
'    5             32            1             32
'    4             16            0             0
'    3             8             1             8
'    2             4             0             0
'    1             2             1             2
'    0             1             0             0
'   Total:                                     746          3/4
'   Decimal:                                   746.75
'
' 2018-03-09. Gustav Brock, Cactus Data ApS, CPH.
'
Public Sub DebugBase2( _
    ByVal Value As Variant, _
    Optional ByVal Exponent As Long)
   
    ' Maximum possible exponent.
    Const MaxExponent2  As Long = 96

    Dim Exponent2       As Long
    Dim Number          As Variant
    Dim Rounded         As Variant
    Dim Factor          As Long
    Dim Sign            As Long
    Dim Numerator       As Long
    Dim Denominator     As Long

    If Not IsNumeric(Value) Then Exit Sub

    Sign = Sgn(Value)
    If Sign = 0 Then Exit Sub
   
    Number = CDec(Fix(Abs(Value)))
   
    ' Split and print the integer part.
    Debug.Print "Exponent", "2 ^ Exponent", "Factor", "Value", "Fraction"
    If Number > 0 Then
        ' Print each bit and value.
        For Exponent2 = Int(Log2(Number)) To 0 Step -1
            If Exponent2 = MaxExponent2 Then
                ' Cannot perform further calculation.
                Factor = 1
                Number = 0
            Else
                Factor = Int(Number / CDec(Base2 ^ Exponent2))
                Number = Number - CDec(Factor * Base2 ^ Exponent2)
            End If
            Debug.Print Exponent2, Base2 ^ Exponent2, Factor, Sign * Factor * Base2 ^ Exponent2
        Next
    Else
        ' Print zero values.
        Debug.Print 0, 0, 0, 0
    End If
   
    ' Find and print the fraction.
    Rounded = ConvertDecimalFractions(Value, Exponent, Numerator, Denominator)
    Debug.Print "Total:", , , CDec(Fix(Value)), Numerator & "/" & Denominator
    Debug.Print "Decimal:", , , Rounded
   
End Sub

Study the in-line comments to follow the steps it takes to break down the value.


It uses a helper function, Log2, to limit the search for components from the maximum of 96 to those that are present:


' Returns Log 2 of Value.
'
' 2018-02-20. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function Log2( _
    ByVal Value As Double) _
    As Double

    ' No error handling as this should be handled
    ' outside this function.
    '
    ' Example:
    '
    '     If MyValue > 0 then
    '         LogMyValue = Log2(MyValue)
    '     Else
    '         ' Do something else ...
    '     End If
   
    Log2 = Log(Value) / Log(Base2)

End Function

If you, for example, call it with these parameters:

DebugBase2 746.873, 2

it will round the value and list the components of the rounded value - and also the rounded value as a decimal number as well as an integer and fraction:

Exponent      2 ^ Exponent  Factor        Value         Fraction
9             512           1             512
8             256           0             0
7             128           1             128
6             64            1             64
5             32            1             32
4             16            0             0
3             8             1             8
2             4             0             0
1             2             1             2
0             1             0             0
Total:                                    746          3/4
Decimal:                                  746.75


Conclusion

Armed with these functions, you can - similar to normal rounding to decimals or integers - easily round values by the power of two with the high precision possible in VBA using Decimal. Also, the result can be a decimal or a set of values for the integer part and the fraction.


Last but not least, the result can be presented or debugged in ways that are easy for the human eye to comprehend.


Further reading

More info on the power of two and rounding can be found at Wikipedia - Power of two


The previous article on decimal rounding:

Rounding values up, down, by 4/5, or to significant figures


The next article - on a practical example of rounding by the power of two:
Convert and format imperial distance (feet and inches) with high precision​​​


Downloads

The current version can always be found at GitHub.

The initial version is here: Rounding 1.3.1.zip This includes a Microsoft Access 2016 project.


I hope you found this article useful. You are encouraged to ask questions, report any bugs or make any other comments about it below.


Note: If you need further "Support" about this topic, please consider using the Ask a Question feature of Experts Exchange. I monitor questions asked and would be pleased to provide any additional support required in questions asked in this manner, along with other EE experts.


Please do not forget to press the "Thumbs Up" button if you think this article was helpful and valuable for EE members.


0
2,367 Views
Gustav BrockMVP
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.