SI Format of floating point number.

Did you ever have a project that required a second to be complete? Then the second project required a third? Well, this a third. Finishing will allow me to to get back to the second.

SI is acronym for International System of units. The letter order of SI is because in French the name of the system is Système International d'unités. Units are second, metre (meter), kilogram, etcetera. There are also derived units like Hertz, Watt and Newton, and more.

The function presented here formats the significand (numeric part) into groups of 3 powers of 10, and adds a prefix letter. It is called a prefix because it is before the unit. The user appends the unit's name or abreviation. See here (in new window or tab) for SI prefix list. SI prefixes for hecto (h), deca (da), deci (d) and centi (c) are not supported. The exponents of ten they represent are not evenly divisable by 3.

For example, there is a signal of 10234000 Hertz. SI prefix formatted it is written, printed or displayed 10.234 MHz.

Jump to source code -
Include file
Code to compile SLL
PBCC demo
PBWin demo

Download compiled SLL in ZIP file (release 20221209).
Download compiled demo programs in a zip file (release 20221209). You don't need a compiler to try either the GUI or console application.


Flow Of The Code
Nunber of digits in result.
Optional. Default is 6, minimum is 3 and maximum 18. Input less than 3 or greater than 18 is corrected to 3 or 18.
Use FORMAT$, get exponent and string pointers.
FORMAT$ returns an integer with the set number of digits, and an exponent so the value is equal to the extended floating point input. The exponent is adjusted so string would be normalized floating point value.
Calculate derived pointers.
These are pLastInChar, the character before "e", and pLenNumOutStr, the location of size in bytes of the result string.
Check is significand is negative.
If negative then input number is not zero. Also set a variable for use later in padding section. If first character is not hyphen, check if it is "1" or greater then skip past "0" format.
Format zero return.
When number in is 0, the result will be "0.0" regardless of the number of digits requested. It will be left padded if DP_Align option is nonzero.
Divide exponent by 3, return quotient and remainder (MODulus).
The exponent is used to select the prefix letter. (Like 6, 7 and 8 are "M" for mega.) Integer division by 3 means one value per prefix. (Making 2 mean "M".) The rermainder (modulo) is used for padding for decimal alignment and number of integer digits. There are problems with negative exponents though, see the following screen capture:
------  Unmodified E Division and MOD, then modified  ------
              Quotient      Remainder     Exponent      Quotient      Remainder
Raw           Of \ 3 Raw    Of \ 3 Raw    Modified      With Modified Also
Exponent      Exponent      Exponent      If Negative   Exponent      Modified
 6             2             0             6             2             0
 5             1             2             5             1             2
 4             1             1             4             1             1
 3             1             0             3             1             0
 2             0 <--§        2             2             0             2
 1             0 <--§        1             1             0             1
 0             0 <--§        0 <--◊        0             0             0
-1             0 <--§       -1 <--‡       -3            -1             2
-2             0 <--§       -2 <--‡       -4            -1             1
-3            -1 <--†        0            -5            -1             0
-4            -1 <--†       -1            -6            -2             2
-5            -1 <--†       -2            -7            -2             1
-6            -2             0            -8            -2             0
(footnote markers edited in)
§ - Quotient of exponents 2 down to -2 all 0; not unique to exponents 0, 1 and 2 which do not get a prefix, and -3, -2 and -1 which should have prefix of "m" for milli.
- Also note that a particular quotient appears for exponents that have different prefixes. Exponents -5 and -4 should be prefix "µ", and -3 prefix "m", but all three exponents have quotient of -1.
- A remainder of 0 can be used to identify that the integer part of the result will have three digits whether the number is positive or negative.
- Remainders -2 and -1 are unique, but identifying the number of digits requires comparing two values, one for positive exponents and another for negative. (For a single digit from a positive is remainder 1 and for a negative is -2)
(Source code for the screen capture.)
      ((It will open in new browser window or tab.))
By subtracting 2 from negative exponents before dividing, the problems noted by "§" and "" are solved. By adding 2 to the remainder after the division, the alignment padding (if option used), and the number of integer digits are easier to select; solving the "" problem.
Note that some other compilers handle MOD of negative differently.
In BASIC \ and MOD (two divisions) are needed to get quotient and remainder. The assembly instruction IDIV (DIV for unsigned) is one division with the results in registers EAX and EDX.
Place pad (if DP_Align), integer digits and decimal point.
Added note: Now that I've edited the diagram and written the code it looks like some repeated code could be reduced by moving the DP_Align check to before the E MOD 3 node. At this point in completion that is a project for another day.
One ternary search node using E MOD 3. Padding is skipped unless DP_Align option is used.
Decimal point ternary tree
There is no decimal point if E MOD 3 = 2 and three digits in result is requested because the is no diget to put after the decimal point.
Append fraction digits.
ISO 31-0 (now ISO 80000-1) specifies that long sequences of digits can be made more readable by placing in groups of (preferably) three with spaces. Commas and periods could be confused for a decimal point, so should not be used as seperators.
This spacing may optionally be disabled. Does not apply to integer portion because the maximum there is three digits. The International System of Units
Append the prefix.
This more resembles a tree, with 9 ternary nodes searching on E\3. The diagram is wide, so I split it to two images to avoid horizontal scrolling.
Positive E ternary tree Negative E ternary tree
Done!
Set function return to the built NumOutStr, and exit.
Finally, the GOTO routine for "Out of prefix range." message.

At the end of the function are appending the prefix to SignificandStr and out of range GOTO code.


SI_PrefixFormat.bas Source Code

Include file has #LINK for SLL, and remarked out DECLARE

'file SI_PrefixFormat.inc
'file SI_PrefixFormat.inc
'Not really needed for one #LINK line. It has to be somewhere.
'
#link "SI_PrefixFormat.sll" 'adjust path as needed
'
'-------------------------------------------------------------------------------
'for info if you only have compiled SLL -
'
'declare function SI_PrefixFormat alias "SI_PrefixFormat" _
'                                          (byref NumToFormat as ext, _
'                                       opt byval DigitsOut as long, _
'                                       opt byval DP_Align as long, _
'                                       opt byval No3DigGrps as long) as wstring
'DigitsOut allows 3 to 18, default 6.
'··3 minimum for hundreds in any prefix.
'····option not used or 0 sets default.
'····1, 2 or negative are corrected to 3.
'··18 is maximum for Extended floating point type.
'····greater than 18 is corrected to 18.
'
'DP_Align, not aligned is default.
'··option not used or 0 for left aligned.
'··non 0 (suggest -1) for left padded with spaces for decimal points alignment.
'··(for best results use non-proportional (fixed width) font)
'
'###############################################################################
'A MACRO for aligning on the decimal point posted elsewhere on PB forum. It
'works with either proportional or non-proportional fonts. If you use that
'MACRO, or similar code, do not use DP_Align option of this function.
'
'No3DigGrps, 3 digit groups (with thousands separators) is default.
'··option not used or 0 for default.
'··non 0 (suggest -1) for no separation.
'-------------------------------------------------------------------------------
'If you'd rather have a DLL than SLL, or pasting the function into your code;
'comment the #LINK line, uncomment the whole DECLARE. In source file change
'#COMPILE SLL to #COMPILE DLL, and COMMON to EXPORT in FUNCTION definition,
'add ALIAS.        

I added the thousands separation to the BASIC code first, when that was working how I wanted I rewrote in assembly. The assembly source code is longer than the BASIC, but compiled to half the size!

'release 20221209, added prefixes Q, R, r and q.
'
'Now thousands separation applied to fraction part of value with spaces by
'default per the General Conference on Weights and Measures. This can optionally
'be disabled. The significand (integer part) does not need thousands separation
'because it is already limited to 1 to 999 in order to use the prefix.
'
'The units, or their abreviations, should be appended directly on the resulting
'wide string. (Byte oriented strings should only used for DIBs, numeric types
'and UTF-8 over a network.)
'
'Description of optional parameters in INCLUDE file in following code block.
'
'Copyleft 2022 by Dale Yarker; do what you want with this code except claim it,
'or sell it, as yours. No warranty of any kind.
#compile sll "SI_PrefixFormat.sll"
#dim all
'
function SI_PrefixFormat(byval NumToFormat as ext, _
                     opt byval DigitsOut as long, _
                     opt byval DP_Align as long, _
                     opt byval No3DigitGrps as long) common as wstring
  #register none
  local NumInStr, NumOutStr as wstring
  local pNumInChar, pNumOutChar, pLastInChar, pLenNumOutStr as dword
  local NumIsNeg, ExpntIsNeg, RemainBytes as long
  local E as long '1st as exponent of normatized number in, then it
                  'is exponent integer divided by 3.
  local E_Mod3 as long 'Remainder of exponent integer divided by 3.
  '------------------------------------------- Check/Set number of DigitsOut. --
  ! push eax
  ! mov eax, DigitsOut
  ! cmp eax, 0&             'optional parameter not used or set to 0
  ! jne DigitsOutNotDefault 'go check minimum
    ! mov DigitsOut, 6&     'set default
    ! jmp DigitsOutDone
  DigitsOutNotDefault:
  ! cmp eax, 3&             'non-valid 1, 2 or negative
  ! jge DigitsOutNotLTMin   'go check maximum
  ! mov DigitsOut, 3&       'set minimum
  ! jmp DigitsOutDone
  DigitsOutNotLTMin:
  ! cmp eax, 18&            'non-valid greater than 18
  ! jle DigitsOutDone       'not above max
  ! mov DigitsOut, 18&      'set maximum
  DigitsOutDone:
  ! pop eax
  '------------------- Binary to string; get last digit, current digit and E. --
  'extended float input to string of integer plus exponent
  NumInStr = format$(NumToFormat, string$(DigitsOut, "#") + "e-##")
  '
  'Exponent of input as integer
  E = val(NumInStr, instr(-1, NumInStr, "e") + 1)
  '
  pNumInChar = strptr(NumInStr)
  NumOutStr = string$(30, $$nul)
  pNumOutChar = strptr(NumOutStr)
 ' ?
  '  ? E, NumInStr
  '  exit function
  '
  ! push eax
  ! push ebx
  ! push ecx
  ! push edx
  ! push edi
  ! push esi
  '------ Set variabes, leave in register if possible, or Format zero result. --
  'calculate pointer to last digit of input string
  ! mov ebx, DigitsOut
  ! sal ebx, 1???
  ! mov ecx, pNumInChar
  ! add ebx, ecx
  ! mov pLastInChar, ebx

 ' ! sal pLastInChar, 1& 'number characters times 2 for number of bytes

  ! sub pLastInChar, 2&
  'result character pointer to register and calculate pointer to
  'pointer to byte length of result string
  ! mov edi, pNumOutChar
  ! mov esi, edi
  ! sub esi, 4& 'is pLenNumOutStr
  'Check for negative
  ! cmp word ptr [ecx], &h002d??   '"-"
  ! je Sgnfcnd_Is_Neg              'can't be 0 if negative
    'Check "1" or greater than
    ! cmp word ptr [ecx], &h0031?? '"1"
    ! jae Sgnfcnd_NotZero
      'CHeck DP_Align
      ! cmp DP_Align, 0&
      ! jne SgnfcndIsZeroIsDPAlgn
      'SgnfcndIsZeroNotDPAlign
        ! mov dword ptr [edi], &h002E0030??? 'zero dp
        ! add edi, 4???
        ! mov  word ptr [edi], &h0030??      'zero
        ! mov dword ptr [esi], &h00000006??? '6 to pLenNumOutStr
      ! jmp FunctionDone
      SgnfcndIsZeroIsDPAlgn:
        ! mov dword ptr [edi], &h00200020??? 'space space
        ! add edi, 4???
        ! mov dword ptr [edi], &h00300020??? 'space zero
        ! add edi, 4???
        ! mov dword ptr [edi], &h0030002E??? 'dp zero
        ! mov dword ptr [esi], &h00000012??? '12 to pLenNumOutStr
      ! jmp FunctionDone
  Sgnfcnd_Is_Neg:
    ! mov NumIsNeg, -1&
  Sgnfcnd_NotZero: 'non-zero to here, positive skips past Sgnfcnd_Is_Neg
  '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ only EBX available ~~~~
  '-------------- Calc E \ 3 and E MOD 3, both modified for negative E MOD 3. --
  ! mov eax, E
  ! add eax, DigitsOut
  ! sub eax, 1&
  ! bt  eax, 31??? 'bit 31 can only be set if LONG is negative
  ! jnc E_IsPositive1
  ! mov ExpntIsNeg, -1&
  ! mov edx, -1& 'make edx upper part of 64 bit 2s compliment to eax's lower
  ! sub eax, 2&  '
  ! jmp DoDivide
  E_IsPositive1:
  ! xor edx, edx 'edx to 0
  DoDivide:
  ! mov ebx, 3& 'divisor
  ! idiv ebx
  ! cmp ExpntIsNeg, 0&
  ! je E_IsPositive2
  ! add edx, 2&
  E_IsPositive2:
  '-------------------- Use E MOD 3 for dp align, and to place decimal point. --
  '. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . is tens . .
  ! cmp edx, 1&
  ! jg HundredsInteger
  ! jl UnitsInteger
    'Tens Padding and minus sign
    ! cmp DP_Align, 0&
    ! je TensNoAlignPad
      'Tens Align Pad
      ! cmp NumIsNeg, 0&
      ! jne TensAlignNegPad
      'TensAlignPosPad
        ! mov dword ptr [edi], &h00200020??? 'space space
        ! add edi, 4&                        'OutChar ptr adjust
        ! mov dword ptr [esi], 4&             'Out Len set
        ! jmp TensIntegerDigits
      TensAlignNegPad:
        ! mov dword ptr [edi], &h002D0020??? 'space minus
        ! add ecx, 2&                        'InChar ptr adjust
        ! add edi, 4&                        'OutChar ptr adjust
        ! mov dword ptr [esi], 4&             'Out Len set
        ! jmp TensIntegerDigits
    TensNoAlignPad:
      ! cmp NumIsNeg, 0&
      ! jne TensNoAlignPadNeg
      'TensNoAlignPosPad
        ! mov dword ptr [esi], 0&             'Out Len set
        ! jmp TensIntegerDigits
      TensNoAlignPadNeg:
        ! mov word ptr [edi], &h002D??       'minus
        ! add ecx, 2&                        'InChar ptr adjust
        ! add edi, 2&                        'OutChar ptr adjust
        ! mov dword ptr [esi], 2&             'Out Len set
    TensIntegerDigits:
      ! mov ebx, dword ptr [ecx]             '2 digits InChar to register
      ! mov dword ptr [edi], ebx             '2 digits register to OutChar
      ! add ecx, 4&                          'InChar ptr adjust
      ! add edi, 4&                          'OutChar ptr adjust
      ! add dword ptr [esi], 4&               'Out Len adjust
      ! jmp DoDecimalPoint
  '· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · is hundreds · ·
  HundredsInteger:
    'Hundreds Padding and minus sign
    ! cmp DP_Align, 0&
    ! je HundredsNoAlignPad
      'Hundreds Align Pad
      ! cmp NumIsNeg, 0&
      ! jne HundredsAlignPadNeg
      'HundredsAlignPadPos
        ! mov word ptr [edi], &h0020??       'space
        ! add edi, 2???
        ! mov dword ptr [esi], 2&
        ! jmp HundredsIntegerDigits
      HundredsAlignPadNeg:
        ! mov word ptr [edi], &h002D??       'minus
        ! add ecx, 2???
        ! add edi, 2???
        ! mov dword ptr [esi], 2&
        ! jmp HundredsIntegerDigits
    HundredsNoAlignPad:
      ! cmp NumIsNeg, 0&
      ! jne HundredsNoAlignPadNeg
      'HundredsnoAlignPadPos
        ! mov dword ptr [esi], 0&             'set length of OutStr to 0
        ! jmp HundredsIntegerDigits
      HundredsNoAlignPadNeg:
        ! mov word ptr [edi], &h002D??       'minus
        ! add ecx, 2???
        ! add edi, 2???
        ! mov dword ptr [esi], 2&
        '"fall" to HundredsIntegerDigits
    HundredsIntegerDigits:
      ! mov ebx, dword ptr [ecx]
      ! mov dword ptr [edi], ebx             '1st and 2nd digits
      ! add ecx, 4&
      ! add edi, 4&
      ! mov  bx, word ptr [ecx]
      ! mov word ptr [edi], bx               '3rd digit
      ! add ecx, 2&
      ! add edi, 2?
      ! add dword ptr [esi], 6&
    ! cmp DigitsOut, 3&
    ! jg DoDecimalPoint
    ! je DoPrefix
    'Error
      ! mov dword ptr [esi], 10&
      ! mov edi, esi
      ! add edi, 4???
      ! mov dword ptr [edi], &h00520045??? 'E R
      ! add edi, 4???
      ! mov dword ptr [edi], &h004F0052??? 'R O
      ! add edi, 4???
      ! mov dword ptr [edi], &h00000052??? 'R nul
      ! jmp FunctionDone
  '· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·  is units · ·
  UnitsInteger:
    ! cmp DP_Align, 0&
    ! je UnitsNoAlignPad
    'UnitsAlignPad
      ! cmp NumIsNeg, 0&
      ! jne UnitsAlignPadNeg
      'UnitsAlignPadPos
        ! mov dword ptr [edi], &h00200020??? 'space space
        ! add edi, 4???
        ! mov word ptr [edi], &h0020??       'space
        ! add edi, 2??
        ! mov dword ptr [esi], 6&
        ! jmp UnitsIntegerDigits
      UnitsAlignPadNeg:
        ! mov dword ptr [edi], &h00200020??? 'space space
        ! add edi, 4???
        ! mov word ptr [edi], &h002D??       'minus
        ! add ecx, 2??
        ! add edi, 2??
        ! mov dword ptr [esi], 6&
        ! jmp UnitsIntegerDigits
    UnitsNoAlignPad:
      ! cmp NumIsNeg, 0&
      ! jne UnitsNoAlignPadNeg
      'UnitsNoAlignPadPos
        ! mov dword ptr [esi], 0&
        ! jmp UnitsIntegerDigits
      UnitsNoAlignPadNeg:
        ! mov word ptr [edi], &h002D??
        ! add ecx, 2??
        ! add edi, 2??
        ! mov dword ptr [esi], 2&
        '"fall" to UnitsIntegerDigits
    UnitsIntegerDigits:
      ! mov bx, word ptr [ecx]              'InChar to register
      ! mov word ptr [edi], bx
      ! add ecx, 2???
      ! add edi, 2???
      ! add dword ptr [esi], 2&
      '"fall" to DoDecimalPoint
  '· · · · · · · · · · · · · · · · · · · · · · · · · ·  append decimal point · ·
  DoDecimalPoint:
    ! mov word ptr [edi], &h002E??  'decimal point
    ! add edi, 2???
    ! add dword ptr [esi], 2&
    '"fall" to FractionDigits
  '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EDX now available ~~~~
  '---------------------------- Fraction digits - continuous or 3 digit groups -
  FractionDigits:
  ! mov ebx, pLastInChar
  ! sub ebx, ecx
  ! add ebx, 2???                            'Remaining Bytes
  '''
  ! cmp No3DigitGrps, 0&
  ! jne FractionGrpsNoTop
    FractionGrpsYesTop:
    ! cmp ebx, 6???
    ! jg FractionGrpsYesGT6
    ! jl FractionGrpsYesLT6
    'FractionGrpsYesET6
      ! mov edx, dword ptr [ecx]       '2 InChars of last 3 char group to EDX
      ! mov dword ptr [edi], edx       'EDX to OutChars
      ! add ecx, 4???                  'adjust ECX point 3rd InChar of group
      ! add edi, 4???                  'adjust EDI point 3rd OutChar of group
      ! mov  dx, word ptr [ecx]        '3rd InChar to DX. Is last, no adjust ECX
      ! mov word ptr [edi], dx         'DX to OutChar
      ! add edi, 2???                  'adjust EDI point space by DoPrefix
      ! add dword ptr [esi], 6&        'adjust LEN OutChars by appended bytes
      ! jmp DoPrefix
    FractionGrpsYesGT6:
      ! mov edx, dword ptr [ecx]       '1st and 2nd InChars to EDX
      ! mov dword ptr [edi], edx       'EDX to OutChars
      ! add ecx, 4???                  'adjust ECX point to 3rd InChar of group
      ! add edi, 4???                  'adjust EDI point to 3rd OutChar of group
      ! mov  dx, word ptr [ecx]        '3rd InChar to DX
      ! mov word ptr [edi], dx         'DX to OutChar
      ! add ecx, 2???                  'adjust ECX point 1st InChar next of group
      ! add edi, 2???                  'adjust EDI point to group separator
      ! mov word ptr [edi], &h0020???  'space
      ! add edi, 2???                  'adjust EDI point 1st OutChar next of group
      ! add dword ptr [esi], 8&        'adjust LEN OutChars by appended bytes
      ! sub ebx, 6???                  'reduce remaining bytes of InChar by 6
      ! jmp FractionGrpsYesTop
    FractionGrpsYesLT6:
      ! cmp ebx, 4???
      ! je FractionGrpsBothET4   'je
      ! jl FractionGrpsBothLT4
  FractionGrpsNoTop:
    ! cmp ebx, 4???
    ! je FractionGrpsBothET4
    ! jl FractionGrpsBothLT4
    'FractionGrpsNoGT4:
      ! mov edx, dword ptr [ecx]
      ! mov dword ptr [edi], edx
      ! add ecx, 4???
      ! add edi, 4???
      ! add dword ptr [esi], 4&
      ! sub ebx, 4???   '6
      ! jmp FractionGrpsNoTop
  'these the same for 3 digit groups and ungrouped
  '--------------------------------------------------------
  FractionGrpsBothET4:
    ! mov edx, dword ptr [ecx]         '2 InChars to EDX
    ! mov dword ptr [edi], edx         'EDX to OutChar. Is last, no adjust ECX
    ! add edi, 4???                    'adjust EDI point space by DoPrefix
    ! add dword ptr [esi], 4&          'adjust LEN OutChars by appended bytes
    ! jmp DoPrefix
  FractionGrpsBothLT4:
    ! mov  dx, word ptr [ecx]
    ! mov word ptr [edi], dx
    ! add edi, 2???
    ! add dword ptr [esi], 2&
    ! jmp DoPrefix
  '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EBX, ECX EDX now available ~~~~
  '------------------------------------------- Use E \ 3 for prefix selection --
  DoPrefix:
    ! cmp eax, 0&
    ! jg PrefixGT0
    ! jl PrefixLt0
    'PrefixET0
      ! mov word ptr [edi], &h0020?? 'space for no prefix unit symbol
      ! add dword ptr [esi], 2&
      ! jmp FunctionDone
    PrefixGT0:
      ! cmp eax, 4&
      ! jg PrefixGT4
      ! jl PrefixLt4
      'PrefixET4
        ! mov dword ptr [edi], &h00540020???        'space T
        ! jmp PrefixLenNumOutStr
      PrefixGT4:
        ! cmp eax, 8&
        ! jg PrefixGT8
        ! jl PrefixLT8
        'PrefixET8
          ! mov dword ptr [edi], &h00590020???      'space Y
          ! jmp PrefixLenNumOutStr
        PrefixGT8:
          ! cmp eax, 10&
          ! jg PrefixGT10
          ! jl PrefixLT10
          'PrefixET10:
            ! mov dword ptr [edi], &h00510020???     'space Q
            ! jmp PrefixLenNumOutStr
          PrefixGT10:
            ! jmp OutOfRange
          PrefixLT10:
            ! mov dword ptr [edi], &h00520020???      'space R
            ! jmp PrefixLenNumOutStr
        PrefixLt8:
          ! cmp eax, 6&
          ! jg PrefixGT6
          ! jl PrefixLT6
          'PrefixET6
            ! mov dword ptr [edi], &h00450020???   'space E
            ! jmp PrefixLenNumOutStr
           PrefixGT6:
             ! mov dword ptr [edi], &h005A0020???   'space Z
             ! jmp PrefixLenNumOutStr
           PrefixLT6:
             ! mov dword ptr [edi], &h00500020???   'space P
             ! jmp PrefixLenNumOutStr
      PrefixLT4:
        ! cmp eax, 2&
        ! jg PrefixGT2
        ! jl PrefixLT2
        'PrefixET2
          ! mov dword ptr [edi], &h004D0020???      'space M
          ! jmp PrefixLenNumOutStr
        PrefixGT2:
          ! mov dword ptr [edi], &h00470020???      'space G
          ! jmp PrefixLenNumOutStr
        PrefixLT2:
          ! mov dword ptr [edi], &h006B0020???      'space k
          ! jmp PrefixLenNumOutStr
  PrefixLT0:
    ! cmp eax, -4&
    ! jl  PrefixLTm4
    ! jg  PrefixGTm4
    'PrefixETm4
      ! mov dword ptr [edi], &h00700020???          'space p
      ! jmp PrefixLenNumOutStr
    PrefixLTm4:
      ! cmp eax, -8&
      ! jl PrefixLTm8
      ! jg PrefixGTm8
      'PrefixETm8
        ! mov dword ptr [edi], &h00790020???        'space y
        ! jmp PrefixLenNumOutStr
      PrefixLTm8:
        ! cmp eax, -10&                                       '
        ! jl PrefixLTm10
        ! jg PrefixGTm10
        'PrefixETm10
           ! mov dword ptr [edi], &h00710020???     'space q
           ! jmp PrefixLenNumOutStr
         PrefixLTm10:
           ! jmp OutOfRange
         PrefixGTm10:
           ! mov dword ptr [edi], &h00720020???     'space r
           ! jmp PrefixLenNumOutStr
      PrefixGTm8:
        ! cmp eax, -6&
        ! jl PrefixLTm6
        ! jg PrefixGTm6
        'PrefixETm6
          ! mov dword ptr [edi], &h00610020???      'space a
          ! jmp PrefixLenNumOutStr
        PrefixLTm6:
          ! mov dword ptr [edi], &h007A0020???      'space z
          ! jmp PrefixLenNumOutStr
        PrefixGTm6:
          ! mov dword ptr [edi], &h00660020???      'space f
          ! jmp PrefixLenNumOutStr
    PrefixGTm4:
      ! cmp eax, -2&
      ! jl PrefixLTm2
      ! jg PrefixGTm2
      'PrefixETm2
        ! mov dword ptr [edi], &h00B50020???        'space µ
        ! jmp PrefixLenNumOutStr
      PrefixLTm2:
        ! mov dword ptr [edi], &h006E0020???        'space n
        ! jmp PrefixLenNumOutStr
      PrefixGTm2:
        ! mov dword ptr [edi], &h006D0020???        'space m
        '"fall" to PrefixLenNumOutStr
  PrefixLenNumOutStr:
    ! add dword ptr [esi], 4&
  FunctionDone:
  ! pop esi
  ! pop edi
  ! pop edx
  ! pop ecx
  ! pop ebx
  ! pop eax
  function = NumOutStr
  '
  exit function '===============================================================
  '
  OutOfRange:
  ! pop esi  '(if here, never gets to POPs above)
  ! pop edi
  ! pop edx
  ! pop ecx
  ! pop ebx
  ! pop eax
  function = "Out of prefix range."$$
end function 


The console demo used for testing during coding.

'PBCC6 application to demonstrate SI_PrefixFormat function.
'Release 20221209.
'file demoSIFormatCC.bas
'Is "brute force", but checks units, tens and hundreds, and rounded to 1000
'from lower for each prefix. Also align decimal points and thousands separation.
'Copyleft 2022 by Dale Yarker; do what you want with this code except claim it
'or sell it as yours. No warranty of any kind.
#compile exe
#dim all
#break on
#include "SI_PrefixFormat.inc"
function pbmain () as long
  con.caption$ = "SI with prefix formating for PBCC demo"
  con.print "### Exponent greater than or equal to zero, and number equals " + _
      "zero. ###"
  con.print 7.99999*10^33##; " " SI_PrefixFormat(7.99999*10^33##, 4, 0)
  con.print " 9.9999999999999999 * 10^32##"; " " _
     SI_PrefixFormat(9.9999999999999999 * 10^32##, 4, -1); _
     "   FORMAT$() rounded to OOR"
  con.print "------------------------- Prefixes added by SI in 2022 -----------------------"
  con.print "Q, DigitsOut MOD3=1, not decimal aligned, thousand no separation"
  con.print SI_PrefixFormat(1.23456789432 * 10^32##, 5, 0, -1)
  con.print SI_PrefixFormat(1.23456789432 * 10^31##, 5, 0, -1)
  con.print SI_PrefixFormat(1.23456789432 * 10^30##, 5, 0, -1)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^29##, 5, 0, -1), _
    "rounded from R to Q"
  con.print "R, DigitsOut MOD3=0,  decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456789432 * 10^29##, 5, -1)
  con.print SI_PrefixFormat(1.23456789432 * 10^28##, 5, -1)
  con.print SI_PrefixFormat(1.23456789432 * 10^27##, 5, -1)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^26##, 5, -1), _
    "rounded from Y to R"
  con.print "------------------------------------------------------------------------------"
  con.print "Y, DigitsOut MOD3=2, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456789432 * 10^26##, 11, -1, 0)
 ' #if 0
  con.print SI_PrefixFormat(1.23456789432 * 10^25##, 11, -1, 0)
  con.print SI_PrefixFormat(1.23456789432 * 10^24##, 11, -1, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^23##, 4, -1, 0), _
    "rounded from Z to Y"
  con.print
  con.print "Z, DigitsOut MOD3=1, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456789432 * 10^23##, 10, -1, 0)
  con.print SI_PrefixFormat(1.23456789432 * 10^22##, 10, -1, 0)
  con.print SI_PrefixFormat(1.23456789432 * 10^21##, 10, -1, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^20##, 4&, -1, 0), _
     "rounded from E to Z"
  con.print
  con.print "E DigitsOut MOD3=0, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456789432 * 10^20##, 9, -1, 0)
  con.print SI_PrefixFormat(1.23456789432 * 10^19##, 9, -1, 0)
  con.print SI_PrefixFormat(1.23456789432 * 10^18##, 9, -1, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^17##, 4&, -1&, 0), _
     "rounded from P to E"
  con.print "------------------------------------------------------------------------------"
  con.print "P, DigitsOut MOD3=2, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456722 * 10^17##, 8, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^16##, 8, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^15##, 8, 0, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^14##, 4, 0, 0), _
     "rounded from T to P"
  con.print
  con.print "T, DigitsOut MOD3=1, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456722 * 10^14##, 7, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^13##, 7, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^12##, 7, 0, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^11##, 4, 0), _
     "rounded from G to T
  con.print
  con.print "G, DigitsOut MOD3=0, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.23456722 * 10^11##, 6, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^10##, 6, 0, 0)
  con.print SI_PrefixFormat(1.23456722 * 10^9##,  6, 0, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^8##, 4&, 0), _
     "rounded from M to G"
  con.print "------------------------------------------------------------------------------"
  con.print "M, negative, DigitsOut MOD2=1, decimal aligned, no thousand separation"
  con.print SI_PrefixFormat(-1.23456722 * 10^8##, 7, -1, -1)
  con.print SI_PrefixFormat(-1.23456722 * 10^7##, 7, -1, -1)
  con.print SI_PrefixFormat(-1.23456722 * 10^6##, 7, -1, -1)
  con.print SI_PrefixFormat(-9.9999999999999999 * 10^5##, 7, -1, -1), _
     "rounded from k to M"
  con.print
  con.print "k, negative, DigitsOut MOD2=0, decimal aligned, no thousand separation"
  con.print SI_PrefixFormat(-1.23456722 * 10^5##, 6, -1, -1)
  con.print SI_PrefixFormat(-1.23456722 * 10^4##, 6, -1, -1)
  con.print SI_PrefixFormat(-1.23456722 * 10^3##, 6, -1, -1)
  con.print SI_PrefixFormat(-9.9999999999999999 * 10^2##, 4&, -1&), _
     "rounded from none to k"
  con.print "------------------------------------------------------------------------------"
  con.print "no prefix, 3 DigitsOut, "
  con.print SI_PrefixFormat(1.2345 * 10^2##, 3, -1, -1), "no decimal point"
  con.print SI_PrefixFormat(1.2345 * 10^1##, 3, -1, 0), "
  con.print SI_PrefixFormat(9.9999999999999999##, 3, -1), "rounded from 9.99 to 10"
  con.print SI_PrefixFormat(1.2345##, 3, -1, 0)
  con.print
  con.print SI_PrefixFormat(0.0##, 5, -1, 0)
  con.print SI_PrefixFormat(0.0##, 4, 0, -1)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^-1##, 4, -1), _
     "rounded from m to none"
  con.print string$$(79, "=") '-------------------------------------------------
  con.print "Exponent less than zero."
  con.print
  con.print "m, negative, DigitsOut MOD3=0, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-1, 18, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-2, 18, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-3, 18, -1)
  con.print SI_PrefixFormat(-9.99999999999999999 * 10^-4##, 18, -1), _
     "didn't round, stayed µ"
  con.print
  con.print "µ, negative, DigitsOut MOD3=2, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-4, 17, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-5, 17, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-6, 17, -1)
  con.print SI_PrefixFormat(-9.99999999999999999 * 10^-7##, 17, -1), _
     "rounded from n to µ"
  con.print
  con.print "n, negative, DigitsOut MOD3=1, decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-7, 16, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-8, 16, -1)
  con.print SI_PrefixFormat(-1.2345678998765432 * 10^-9, 16, -1)
  con.print SI_PrefixFormat(-9.99999999999999999 * 10^-10##, 16, -1), _
     "rounded from p to n"
  con.print "------------------------------------------------------------------------------"
  con.print "p, negative, DigitsOut MOD3=0, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.234567890987654 * 10^-10, 15, 0)
  con.print SI_PrefixFormat(-1.234567890987654 * 10^-11, 15, 0)
  con.print SI_PrefixFormat(-1.234567890987654 * 10^-12, 15, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^-13##, 15,0), _
     "rounded from f to p"
  con.print
  con.print "f, negative, DigitsOut MOD3=2, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.23456789098765 * 10^-13, 14, 0)
  con.print SI_PrefixFormat(-1.23456789098765 * 10^-14, 14, 0)
  con.print SI_PrefixFormat(-1.23456789098765 * 10^-15, 14, 0)
  con.print SI_PrefixFormat(-9.9999999999999999 * 10^-16##, 14, 0), _
     "rounded from a to f"
  con.print
  con.print "a, negative, DigitsOut MOD3=1, not decimal aligned, thousand separation"
  con.print SI_PrefixFormat(-1.2345678909876 * 10^-16, 13, 0)
  con.print SI_PrefixFormat(-1.2345678909876 * 10^-17, 13, 0)
  con.print SI_PrefixFormat(-1.2345678909876 * 10^-18, 13, 0)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^-19##, 4, 0), _
     "rounded from z to a"
  con.print "------------------------------------------------------------------------------"
  con.print "z,  DigitsOut MOD3=0, not decimal aligned, no thousand separation"
  con.print SI_PrefixFormat(1.2345 * 10^-19, 12, 0, -1)
  con.print SI_PrefixFormat(1.2345 * 10^-20, 12, 0, -1)
  con.print SI_PrefixFormat(1.2345 * 10^-21, 12, 0, -1)
  con.print SI_PrefixFormat(9.9999999999999999 * 10^-22##, 12, 0, -1), _
     "rounded from y to z"
  con.print
  con.print "y, DigitsOut MOD3=2, not decimal aligned, no thousand separation"
  con.print SI_PrefixFormat(1.2345 * 10^-22, 11, 0, -1)
  con.print SI_PrefixFormat(1.2345 * 10^-23, 11, 0, -1)
  con.print SI_PrefixFormat(1.2345 * 10^-24, 11, 0, -1)
  con.print "------------------------- Prefixes added by SI in 2022 -----------------------"
  con.print "r, DigitsOut MOD=2 , decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.2345678976 * 10^-25, 10, -1)
  con.print SI_PrefixFormat(1.2345678976 * 10^-26, 10, -1)
  con.print SI_PrefixFormat(1.2345678976 * 10^-27, 10, -1)
  con.print "q, DigitsOut MOD=1 , decimal aligned, thousand separation"
  con.print SI_PrefixFormat(1.2345678976 * 10^-28, 10, -1)
  con.print SI_PrefixFormat(1.2345678976 * 10^-29, 10, -1)
  con.print SI_PrefixFormat(1.2345678976 * 10^-30, 10, -1)
  con.print
  con.print SI_PrefixFormat(7.654321 * 10^-31), "7.654321 * 10^-31 OOR"
  waitkey$
end function


A GUI demo for folks not having PBCC.

'PBWin10 application to demonstrate SI_PrefixFormat function.
'Release 20221018. Option for no thousands separators added.
'Copyleft 2022 by Dale Yarker; do what you want with this code except claim it,
'or sell it, as yours. No warranty of any kind.
'file demoSIFormatWin.bas (in my project folder)
#compile exe
#dim all
'cb selected (consecutive CASEs better for AS CONST, else use AS LONG)
%ID_FormatBtn    = 1001
%ID_ExitBtn      = 1002
'not cb selected
%ID_DigitsTxtBx   = 1100
%ID_DPAlignCkBx   = 1101
%ID_DigitsLbl     = 1102
%ID_No3DigGrpCkBx = 1103
%ID_NumberLbl     = 1104
%ID_NumberTxBx    = 1105
%ID_SINumLbl      = 1106
%ID_SINumTxtBx    = 1107
#include "SI_PrefixFormat.inc"  '
callback function MainDlgCB() as long
  local TempStr, SI_Str as wstring
  local TempLg, Digits, DPAlign, No3DigGrps as long
  local NumExt as ext
  if cb.msg = %wm_command then
    select case as const cb.ctl
      case %ID_FormatBtn
        if cb.ctlmsg = %bn_clicked then 'has non number used characters
          control get text cb.hndl, %ID_NumberTxBx to TempStr
          if verify(TempStr, "0123456789.-+Ee"$$) then
            msgbox "May only contain 0123456789.-+Ee" + $$crlf + _
                   "Edit, then retry Format.", _
                   %mb_ok or %mb_iconerror or %mb_taskmodal, _
                   "Number entry error."$$
            control set focus cb.hndl, %ID_NumberTxBx
          else 'has only number used characters
            NumExt = val(TempStr)
            control get text cb.hndl, %ID_DigitsTxtBx to TempStr
            Digits = val(TempStr)
            control get check cb.hndl, %ID_DPAlignCkBx to DPAlign
            control get check cb.hndl, %ID_No3DigGrpCkBx to No3DigGrps
            SI_Str = SI_PrefixFormat(NumExt, Digits, DPAlign, No3DigGrps)
            control get text cb.hndl, %ID_SINumTxtBx to TempStr
            if TempStr = "" then
              Tempstr = SI_Str
            else
              TempStr += $$crlf + SI_Str
            end if
            control set text cb.hndl, %ID_SINumTxtBx, TempStr
            control set focus cb.hndl, %ID_NumberTxBx
          end if
        end if
      case %ID_ExitBtn
        dialog end cb.hndl
    end select
  end if
end function
function pbmain () as long
  local hMainDlg, hFixedFont as dword
  dialog default font "Microsoft Sans Serif",  12, 0, 1
  font new "Consolas", 12, 0, 1, 1, 0 to hFixedFont
  dialog new 0, "SI with prefix formating for PBWin demo.", , , _
     150, 141 to hMainDlg
  '
  control add label, hMainDlg, %ID_DigitsLbl, "Digits in output:"$$, _
     5, 5, 50, 11
  control add textbox, hMainDlg, %ID_DigitsTxtBx, "", 56, 5, 15, 11, _
     %es_autohscroll or %es_left or %es_number or %ws_border or%ws_tabstop, _
     %ws_ex_clientedge or %ws_ex_left
  control set font hMainDlg, %ID_DigitsTxtBx, hFixedFont
  control add checkbox, hMainDlg, %ID_DPAlignCkBx, "DP Align:", 105, 5, 40, 11, _
     %bs_lefttext or %bs_right or %bs_vcenter or %ws_tabstop, %ws_ex_left
  control add checkbox, hMainDlg, %ID_No3DigGrpCkBx, _
     "Thousands Separation Off:", 50, 21, 95, 11, _
      %bs_lefttext or %bs_right or %bs_vcenter or %ws_tabstop, %ws_ex_left
  control add label, hMainDlg, %ID_NumberLbl, _
     "Enter number then press "$$ + $$dq + "Format"$$ + $$dq, 5 , 37, 110, 11
  control add textbox, hMainDlg, %ID_NumberTxBx, ""$$, 5, 48, 110, 11
  control set font hMainDlg, %ID_NumberTxBx, hFixedFont
  control add button, hMainDlg, %ID_FormatBtn, "Format"$$, 115, 48, 30, 11
  control add label, hMainDlg, %ID_SINumLbl, "SI prefix formated numbers", _
     5, 64, 90, 11
  control add textbox, hMainDlg, %ID_SINumTxtBx, "", 5, 75, 140, 45, _
     %es_autohscroll or %es_left or %es_multiline or %es_readonly or _
     %ws_border or %ws_vscroll, %ws_ex_clientedge or %ws_ex_left
  control set color hMainDlg, %ID_SINumTxtBx, -1, &h00F8F8F8
  control set font hMainDlg, %ID_SINumTxtBx, hFixedFont
  control add button, hMainDlg, %ID_ExitBtn, "Exit", 115, 125, 30, 11
  dialog show modal hMainDlg call MainDlgCB
  font end hFixedFont
end function 


Created on 04 September 2022, updated 09 Dec 2022.

To Domain Home.

home
To Dale's Notebook index.
notebook
To Programs index.
programs