Rounding numbers

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

@caseih Nice one. You have solved the rounding problem.
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

@neil
Print Using also works quite well.
However, it makes similar errors as printf and sprintf, although it does not seem to use these functions.

Code: Select all

Dim a As Double

a = 997.935
Print "original number ";a
Print Using "###.##"; a
Print String(20,"-")

a = -643.435
Print "original number ";a
Print Using "####.##"; a
Print String(20,"-")

Sleep
@caseih
I have tried it out. For me, printf then only makes errors.

@neil
We can't get any further with such snapshots.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

Here's something to read.
Solutions for floating point rounding errors.
https://softwareengineering.stackexchan ... ing-errors
caseih
Posts: 2159
Joined: Feb 26, 2007 5:32

Re: Rounding numbers

Post by caseih »

The root problem is that although both printf and print using do round, since the numbers are binary floating point the number might be in reality a tiny bit lower than the digit 5. For example you might think the number is 0.635, but it might actually 0.6349999. Adding a 5 in the correct digit does help with that apparently. I'm not sure there are other consequences of doing that.

This works for me:

Code: Select all

#include "crt/stdio.bi"

printf(!"%.2f\n",0.634999 + 0.005)
Last edited by caseih on Feb 07, 2024 2:17, edited 1 time in total.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

This does seem to work OK.

Code: Select all

#include "crt/stdio.bi"

printf(!"original number %.6f\n",0.634999)
printf(!"adding to number %.2f\n",0.634999 + 0.005)

sleep
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

You could test for the error. Then only add "0.005" if it's needed.
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

How do you want to test for errors in a program that outputs many rounded numbers?

Errors occur anyway when calculating with doubles, so small rounding errors are irrelevant.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

You need to analyze your program in its successive stages for use in the next step of the program.
Then you will find a solution to the rounding errors.

For testing numbers. Here's a rounding number calculator.
https://www.calculatorsoup.com/calculat ... umbers.php
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

What should I say to that?
Last edited by hhr on Feb 29, 2024 9:12, edited 2 times in total.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

@hhr I don't think you understood me correctly. You mentioned that rounding errors are common. Therefore, correct the mistakes before moving on to the second section of your program. Put differently, wait till they are corrected before printing them on the screen. I know you will eventually figure out how to do this.
neil
Posts: 594
Joined: Mar 17, 2022 23:26

Re: Rounding numbers

Post by neil »

I was thinking of testing the numbers, something like this.

Code: Select all

' test if last digit is odd only then add 0.005

Dim AS Double a,b
Dim As string sa,s1

sa = "":s1 = ""
'odd number
a = 997.935
sa = str(a)
s1 = right(sa,1)
b = val(s1)
Print "original number ";a
if (b mod 2) <> 0 Then a += 0.005
Print Using "###.##"; a
Print String(20,"-")

sa = "":s1 = ""
'odd number
a = 43.435
sa = str(a)
s1 = right(sa,1)
b = val(s1)
Print "original number ";a
if (b mod 2) <> 0 Then a += 0.005
Print Using "##.##"; a
Print String(20,"-")

sa = "":s1 = ""
'even number
a = 43.634
sa = str(a)
s1 = right(sa,1)
b = val(s1)
Print "original number ";a
if (b mod 2) <> 0 Then a += 0.005
Print Using "##.##"; a
Print String(20,"-")

Sleep
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

Unfortunately, your suggestion does not work if the last digit is 1 or 3.

Line 10 in the following suggestion seems to work.
The disadvantage is that DecimalPlaces must be equal to the number of decimal places required in Print Using.

Code: Select all

Dim As Double n,a(1 To ...) = {997.931, 997.932, 997.933, 997.934, 997.935, 997.936, 997.937, 997.938, 997.939}

Dim As Integer DecimalPlaces

For i As Long = Lbound(a) To Ubound(a)
   n = a(i)
   
   Print "Original number ";n
   DecimalPlaces = 2
   If (Instr(Str(n),".") > 0) Andalso (Right(Str(n),1) = "5") Andalso (Len(Str(n)) - Instr(Str(n),".") = DecimalPlaces + 1) Then n += Sgn(n) * 5 * (10^(-DecimalPlaces - 1))
   Print Using "###.##";n
   Print String(20,"-")
   
Next i
Sleep
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

This also works with sprintf:

Code: Select all

#Include "crt.bi"

Dim As Double n,a(1 To ...) = {997.931, 997.932, 997.933, 997.934, 997.935, 997.936, 997.937, 997.938, 997.939}
Dim As Zstring*20 zs
Dim As Integer DecimalPlaces

For i As Long = Lbound(a) To Ubound(a)
   n = a(i)
   
   Print "Original number ";n
   DecimalPlaces = 2
   If (Instr(Str(n),".") > 0) Andalso (Right(Str(n),1) = "5") Andalso (Len(Str(n)) - Instr(Str(n),".") = DecimalPlaces + 1) Then n += Sgn(n) * 5 * (10^(-DecimalPlaces - 1))
   sprintf(zs,"%.*f",DecimalPlaces,n)
   print zs
   Print String(20,"-")
   
Next i
Sleep
I have tested with the following program. If you have seen enough errors, please activate line 92:

Code: Select all

#Include "crt.bi" 
Function sprintfTest(number As Double, decimals As Double) As String
   Dim As String formatstring,exponent
   Dim As Zstring*50 zs
   Dim As Double n
   
   zs = Str(number) 'Copy the number as string
   
   If Instr(zs,"e") Then 'Separate mantissa and exponent
      exponent = Right(zs,Len(zs)-Instr(zs,"e")+1)
      zs = Rtrim(zs,exponent)
   End If
   
   n = Val(zs)
   formatstring = "%." & Str(decimals) & "f"
   sprintf(zs,formatstring,n)
   
   If Instr(zs,".") Then 'If decimal point is present
      zs = Rtrim(zs,"0") 'Remove the zeros at the end, if present
      zs = Rtrim(zs,".") 'Remove the point at the end, if present
   End If
   
   zs = zs & exponent
   'A positive number should be preceded by a space:
   If (Left(zs,1) <> "-") Then zs = Space(1) & zs
   
   Return zs
End Function

Function RoundDouble(number As Double, decimals As Double) As String
   Dim As String s,sign,exponent
   Dim As Integer dp
   Dim As Byte i,carry
   
   s = Str(number) 'Copy the number as string
   
   If Left(s,1) = "-" Then 'Remove the sign
      sign = "-"
      s = Ltrim(s,"-")
   Else
      sign = Space(1)
   End If
   
   If Instr(s,"e") Then 'Separate mantissa and exponent
      exponent = Right(s,Len(s)-Instr(s,"e")+1)
      s = Rtrim(s,exponent)
   End If
   
   dp = Instr(s,".") 'decimal point
   
   If dp > 0 Then 'If decimal point present
      
      i = dp + Abs(decimals) 'Determine the first carry
      If s[i] >= 53 Then carry = 1 'Chr(53)="5"
      s = Left(s,i) ' Remove omitted digits
      
      While (carry = 1)
         i -= 1
         If i = -1 Then s = "1" & s : Exit While 'The carry has run through all the digits, can happen with many nines.
         If Chr(s[i]) = "." Then i -= 1 'Skip the decimal point
         If s[i] < 57 Then 'Chr(57)="9"
            s[i] += 1
            carry = 0
         Else
            s[i] = 48 'Chr(48)="0", Carry over remains 1
         End If
      Wend
      
      s = Rtrim(s,"0")
      s = Rtrim(s,".")
   End If
   
   'If s = "0" Then sign = Space(1) 'The zero has no sign
   s = sign & s & exponent
   
   Return s
End Function

'testing:
'Randomize
Dim As Double n,nsign,e,esign,decimals
Dim As String s1,s2
Do
   'Make up the number:
   n = Fix(Rnd*(10^Int(10*Rnd)))/(10^Int(5*Rnd))
   nsign = Iif(Rnd<0.5,-1,1)
   n = nsign*n
   decimals = Int(Rnd*5)
   'Printout:
   Print "Number: ";Space(5);n,"decimals: ";decimals
   s1 = RoundDouble(n,decimals)
   'If (Instr(Str(n),".") > 0) Andalso (Right(Str(n),1) = "5") Andalso (Len(Str(n)) - Instr(Str(n),".") = Decimals + 1) Then n += Sgn(n) * 5 * (10^(-Decimals - 1))
   s2 = sprintfTest(n,decimals)
   Print "RoundDouble: ";s1
   Print "sprintfTest: ";s2
   If s1<>s2 Then Print "Different, any key to continue...":Sleep
   Print String(30,"-")
Loop
This should not obscure the fact that this is not a correction.
It's tinkering from the outside because you don't know what else to do.
In the time you spend on this, you can create a function yourself.

In addition, this does not fix all errors. If you want to see another error, change line 88: decimals = Int(Rnd*10)
hhr
Posts: 216
Joined: Nov 29, 2019 10:41

Re: Rounding numbers

Post by hhr »

@ badidea, caseih, neil
Thanks for the helpful comments.
srvaldez
Posts: 3390
Joined: Sep 25, 2005 21:54

Re: Rounding numbers

Post by srvaldez »

@coderJeff
I tried hhr suggestion and it seems that he is right, however, I don't know if there are cases where the suggested change would cause a problem.
the format function is a complex beast and it seems that it's not finished, I know that it fails the tests with gcc-13.2 and up
hhr wrote: Jan 09, 2024 14:25 I have now built FreeBASIC in Windows and looked at the bug in src/rtlib/str_format.c again.
As expected, Format shows the same behavior in Windows as in Linux.
It concerns the lines 325-336:

Code: Select all

#if 0
           /* can't scale? */
           if( (pInfo->num_digits_frac == 0 ) ||
               (-ExpValue > pInfo->num_digits_fix +
                            pInfo->num_digits_frac -
                            pInfo->num_digits_omit) )
              value = 0.0;
           else
              value *= pow( 10.0, -ExpValue + pInfo->num_digits_fix );
#else
              value = 0.0;
#endif
I can either delete this section or comment out line 335 to eliminate the error.
Is it possible that this section was used for testing and was simply forgotten to be deleted?

Should we ask the FreeBASIC developers to make this change?

Does anyone have another suggestion on how to fix this bug?

After the correction, the following deviation remains, which only occurs if I want to round to 8 or more digits
and which I do not consider important to change:

Code: Select all

Nr.: 30071
decimals:  9
Number:      -1653.0836885795
Format:      -1653.083688579
Print Using: -1653.083688580
Different, any key to continue...
------------------------------
Nr.: 66610
decimals:  9
Number:      -7584.9011936225
Format:      -7584.901193622
Print Using: -7584.901193623
Different, any key to continue...
------------------------------
Nr.: 394073
decimals:  9
Number:      1193.5064336285
Format:      1193.506433628
Print Using: 1193.506433629
Different, any key to continue...
------------------------------
Nr.: 918772
decimals:  9
Number:      958.3329197485
Format:      958.332919748
Print Using: 958.332919749
Different, any key to continue...
I consider the other differences to be a matter of opinion.
Finally, the character string output by Format can still be post-processed.
Post Reply