Big problem with fp plugin and Integer divide

Just calling attention

No actually not, my background us not math. So all advises from you math guys is the only way we can fix math problems u-in this plugin.

I can fix structural issues and other such but not the math.

1 Like

So his modulus ends here

void myModulus(mb& z, const mb& x, const mb& n)
{
	static mb		temp, nn;
	static bool		initGood=false;
		
	if(!initGood)
	{
		init(temp);
		initGood = true;
	}
	
	if(!n.n)
		return;
	
	if(!x.n)
	{
		z.n = 0;
		return;
	}
	
	nn = abs(n);
	
	divRem(temp, z, x, nn);
	
	if(x.n<0 && nn.n>0)
		add(z, z, nn);
	
	if(z>nn/2)
		z = z - nn;
	 
	/*
	if(z==nn)
		z = 0;
     */
		
}/* myModulus */

Original idea was to just apply the workaround like this

void myModulus(mb& z, const mb& x, const mb& n)
{
	static mb		temp, nn;
	static bool		initGood=false;
		
	if(!initGood)
	{
		init(temp);
		initGood = true;
	}
	
	if(!n.n)
		return;
	
	if(!x.n)
	{
		z.n = 0;
		return;
	}
	
	nn = abs(n);
	
	divRem(temp, z, x, nn);
	
	if(x.n<0 && nn.n>0)
		add(z, z, nn);
	
	if(z>nn/2)
		z = z - nn;

	if (z < mb(0.0))
	{
		z = z + n;
	}
	 
	/*
	if(z==nn)
		z = 0;
     */
		
}/* myModulus */

What Robert discovered:

That means that for a “r = x mod y” when x is 5 to 9 and y = 10, when r is negative, add y to fix it ok?

But… what happens when y <> 10?

And using the original workaround rule, the things get broken, for example, for x=-11 and y=-5 if returning the correct r=-1, triggering the rule, and adding y, and instead of -1 we could end with -6

That was my worrying. A set of tests must be done after implementation.

My test so far was on 11 mod 3 I think it was.

But I guess large random set could be generated in code with Xojo integer and one could execute Xojo mod on it, and then feed same set to the BigInteger and let it check if we get same value

That’s a good idea. You must assure diverse positives and negatives x and y.

As Rick surmised, if the result is negative it’s probably necessary to add back the modulus value, not the fixed value 10. But better to find the reason why it’s happening in the first place if possible, and fix it at the source.

It would help to have the definition of mb& (which I assume is the bigInteger data structure or class) and also the code for divRem(). I’ve done almost no programming in C syntax languages, so it’s a slow process for me to read the code, but I’d be happy to poke through it.

Should also note that there appears to be some difference of opinion about how to handle the sign of the result if either or both of the operands are negative. I believe Xojo’s standard mod operator handles it differently than Excel’s mod function. In any event, if both operands are positive, the result should always be positive.

The code is not exactly nice to go through, not exactly normal c coding styles, here is divRem.

The mb class is huge, probably easier to view that on our repo than here.

// z = x/y with remainder; assume x and y are normalized
// if abs(y.n) = 1 we do short division, else INT32 division
bool divRem(mb& z, mb& rem, const mb& x, const mb& y)
{
	mb			xt, yt, zt;
	
	if(!y.n)
		return false;
	
	if(!x.n)
	{
		z.n = 0;
		rem.n = 0;
		return true;
	}
	
	equate(xt, x);
	equate(yt, y);
	
	if(abs(y.n)==1)
	{
		rem.n = 1;
		update(rem);
		rem.b[0] = mbShortDiv(z, xt, yt);
		if(!rem.b[0])
			rem.n = 0;
		if(xt.n<0)
			rem.n = -abs(rem.n);
		return true;
	}
	
	if(!div(z, xt, yt))
	{
		z.n = 0;
		rem.n = 0;
		return false;
	}
	
	equate(zt, z);
	
	mul(rem, zt, yt);
	sub(rem, xt, rem);
	
	mbNormalize(rem);
	
	return true;
	
}/* divRem */

Here is my algorithm for mod after pondering a bit about it:


// r = mod(x, y)  -->

If x = NaN or y = NaN Then   return NaN

If y = 0 Then   return NaN

If x = 0 Then   return 0

Var  ay = abs(y)
Var  minusdiv = -( abs(x) / ay)

If x > 0 and y > 0  then  return ( x + ay * ceil(minusdiv)  )

If x < 0 and y < 0 then return ( x – ay * ceil(minusdiv) )

If y < 0 then return ( x + ay * floor(minusdiv) )   // x > 0 and y < 0

return ( x – ay * floor(minusdiv) )   // x < 0 nd y > 0

Both floating and integer algorithms as Xojo functions:

Public Function fMod(x As Double, y As Double) As Double
  // r = mod(x, y)  -->
  
  If x.IsNotANumber or  y.IsNotANumber Then   return Sqrt(-1) // Nan
  
  If y = 0 Then   return Sqrt(-1) // Nan
  
  If x = 0 Then   return 0
  
  Var  ay As Double = abs(y)
  Var  minusdiv As Double = -( abs(x) / ay)
  
  If x > 0 and y > 0  then  return ( x + ay * ceiling(minusdiv)  )
  
  If x < 0 and y < 0 then return ( x - ay * ceiling(minusdiv)  )
  
  If y < 0 then return ( x + ay * floor(minusdiv) )   // x > 0 and y < 0
  
  return ( x - ay * floor(minusdiv) )   // x < 0 nd y > 0
  
End Function


Public Function iMod(x As Int64, y As Int64) As Int64
  // i = mod(x, y)  -->
  
  // Integer mod
  
  If y = 0 Then Return 0
  
  Var t As int64 = x / y   // gets quotient
  t = t * y                // multiple of y nearest to x 
  
  Return (x - t)  // remainder
  
End Function

I dont know why his BigInteger mod is so complex…in theory it should be just like the Int64 one you put above.

Though yours does not handle if either x or y is negative I suppose…

I did few tests with my algorithms, couldn’t find fails ( yet :smiley: , I’ll let others to find any ) for any mix of positive and negatives.

But right now I’ll let you, Robert and others go beyond exploring the C code.

You could add this precondition as well

If y = 1 Then   return 0
1 Like

I’m looking at the repo now, but there are a lot of files and I’m not sure which source file the mb definition is in. Could you point me to the correct file? Thanks.

Its in mb.h and mb.cpp

Oops, I guess that should have been obvious, but I never noticed those file names. Thanks. I’ll have a look through them.

At once:

If x = 0 or y = 1 Then   return 0

I’ve learned much when I read your posts here about mod, but I don’t understand everything. Rick, Robert and Björn: is there any chance to format the output in a textfield like Format(…,".####…") in Xojo when I use the fp plugin? At least I want to separate every 3 digits like this 123.345.567 and so on. Maybe I don’t see it, but I’ve searched and didn’t find any solution.

I imagine you could at least manually post process it with some string magic touches.

The fpPlugin implementation of the Str() function doesn’t allow for a format argument, and there is no Format() function at all in the fpPlugin. When I wrote the bigFloat version of my Expression Evaluator class, I tried to address this by adding a bigFloat Format() function that at least partially emulates XOJO’s Format() function. It would have been a lot easier if I’d had access to the information on how the bigFloat data type was implemented so that I could directly access the bigFloat data. Unfortunately, all I could work with was the output of the bigFloat Str() function, which already performs some formatting. So, my Format() function has to undo some of this formatting in order to get the raw value and then reformat it according to the user’s format string.

Adding to this difficulty is the fact that XOJO’s Format() function is a functional copy of Microsoft Basic’s Format() function (for language compatibility reasons) which is rather inconsistent and unintuitive in its operation. By comparison, most other non-BASIC languages have Format() functions that follow the user’s format argument string in a very strict and consistent way. Because of this confusing nonintuitive behaviour, I only implemented a subset of the Microsoft/XOJO Format() formatting options.

Now that you’ve been warned, here is the XOJO code for my bigFloat Format() function, which may be a reasonable starting point for implementing what you need. You use it the same way as XOJO’s Format() function with the same format string, except for certain unimplemented formats as noted in the code comments.

No copyright restrictions, although I’d appreciate being credited as the original author.

Public Shared Function nFormat(n As BigFloat, fmt As String) as String
  '
  'This function performs some of the same formatting operations as XOJO's Format()
  'function for the BigFloat data type.
  '
  ' Original code by Robert Weaver, 2021-12-06
  
  'Note that the results of formatting can be very strange if the user
  'randomly intersperses 0's and #'s in the format string.
  'The following code attempts to emulate what the XOJO format function does,
  'within reason.
  'From observation of the XOJO format function:
  'If there is at least one 0 or # before the decimal point, then the entire integer
  'part of the number will be displayed. Leading zeros will be included if 0's
  'are included in the format string. The number of zeros in the format string are
  'counted (even if interspersed with #'s) and this number is the number of digits
  'to be displayed. If the integer part of the number has fewer digits than this,
  'then it is padded with leading zeros.
  
  const kDigits="0123456789\."
  
  'Simplest case is empty format string, so just return the unformatted str() value
  if fmt="" then Return str(n)
  
  'The following code should probably be replaced with a digit by digit parsing scheme.
  'But that will have to be a future project.
  
  static decSepChar As String = Xojo.Core.Locale.Current.DecimalSeparator
  
  'Pick format choice depending on the sign of the number
  dim fm() As String = Split(fmt,";")
  dim nf As Integer = UBound(fm)
  dim f As String = trim(fm(0))
  if n<0 and nf>0 then
    f=trim(fm(1))
  ElseIf n=0 and nf>1 then
    f=trim(fm(2))
  end if
  if f="" then return ""
  
  
  'Get numeric string as array with element 0 as significand and element 1 as exponent.
  'This is done in order to determine where to round the number
  dim nsRaw() As String = split(str(n)+"e0","e")
  'Adjust exponent from position of decimal point
  dim sgnfcnd As String = Filter(trim(nsRaw(0)),kDigits,True)
  'Remove leading zeros from significand.
  'There should never be more than one, but this code should be just as
  'efficient as a single if statement, and will handle multiple leading zeros.
  while Left(sgnfcnd,1)="0" 
    sgnfcnd = mid(sgnfcnd,2)
  wend
  'Determine an exponent adjustment
  dim expAdj As Integer = InStr(sgnfcnd+".",".")-1
  if expAdj=0 then
    'The decimal point is the first character, so count the zeros
    'following the decimal point
    sgnfcnd=mid(sgnfcnd,2)
    While left(sgnfcnd,1)="0"
      sgnfcnd=mid(sgnfcnd,2)
      expAdj=expAdj-1
    Wend
  end if
  dim expnt As String = trim(nsRaw(1))
  dim nPwr As Integer = expAdj+val(expnt)
  'Now remove decimal point if it still exists, so that sgnfcnd is just a string of digits
  sgnfcnd=ReplaceAll(sgnfcnd,".","")
  
  'Parse format string
  
  dim escFmt As String = ReplaceAll(f,"\e","\@") 'escape literal "e"
  dim showExp As Boolean = InStr(escFmt,"e")>0
  
  dim fSgn As String = ""
  if InStr("+-",left(f,1))>0 then fSgn=left(f,1)
  if fSgn="+" then fSgn="+-"
  dim lftParen,rgtParen As string = ""
  if len(Filter(f,"()",true))>0 then
    lftParen="("
    rgtParen=")"
  end if
  'Filter out previously parsed characters
  'Currently no support for "(),%" or embedded text
  f=Filter(f,"0#.", true)
  if f="" then return ""
  
  'Split format string into significand part and fraction part
  '
  
  fm=Split(f,".")
  dim fs As String = fm(0)
  dim ff As String = ""
  if UBound(fm)>0 then
    ff=fm(1)
  end if
  dim nLeadingZeros As Integer = len(Filter(fs,"0",true))
  dim nWholeFieldWidth As Integer = len(Filter(fs,"0#",true))
  dim nMinDecDigits As Integer =0 
  dim nMaxDecDigits As Integer = 0
  dim nRoundPosn As Integer = 0
  
  'Determine rounding position
  nMinDecDigits = len(Filter(ff,"0",true))
  nMaxDecDigits = len(ff)
  if showExp then
    nRoundPosn= -nMaxDecDigits-1
  Else
    nRoundPosn= -(nPwr+len(ff))
  end if
  
  dim sn As String = "+"
  dim nRounded As BigFloat
  if n<0 then
    sn="-"
    nRounded = n - .5*10^(nRoundPosn+nPwr)
  ElseIf n>0 then
    nRounded = n + .5*10^(nRoundPosn+nPwr)
  Else
    'n=0
    nRounded=n
  end if
  sn=Filter(sn,fSgn,true)
  '
  'Now parse the rounded value
  nsRaw() = split(str(nRounded)+"e0","e")
  'Adjust exponent from position of decimal point
  sgnfcnd = Filter(trim(nsRaw(0)),kDigits,True)
  'Remove leading zeros from significand.
  'There should never be more than one, but this code should be just as efficient as a single if statement.
  while Left(sgnfcnd,1)="0" 
    sgnfcnd = mid(sgnfcnd,2)
  wend
  'Determine an exponent adjustment
  expAdj = InStr(sgnfcnd+".",".")-1
  if expAdj=0 then
    'The decimal point is the first character, so count the zeros
    'following the decimal point
    sgnfcnd=mid(sgnfcnd,2)
    While left(sgnfcnd,1)="0"
      sgnfcnd=mid(sgnfcnd,2)
      expAdj=expAdj-1
    Wend
  end if
  
  expnt = trim(nsRaw(1))
  nPwr = expAdj+val(expnt)
  'Now remove decimal point so that sgnfcnd is just a string of digits
  sgnfcnd=ReplaceAll(sgnfcnd,".","")
  
  'Now assemble the output string
  if showExp then
    'This is the simplest case
    dim sgDecFrac As String = mid(sgnfcnd,2,nMaxDecDigits)
    'Remove trailing zeros
    while right(sgDecFrac,1)="0" and len(sgDecFrac)>nMinDecDigits
      sgDecFrac=left(sgDecFrac,len(sgDecFrac)-1)
    wend
    return lftParen+sn+Left(sgnfcnd,1)+decSepChar+sgDecFrac+"e"+str(nPwr-1)+rgtParen
  Else
    'Check to see if number will fit in field
    if nWholeFieldWidth<nPwr then
      'Too big
      return "*********"
    Else'If nPwr>0 then
      'Number should fit in the field
      dim sgWhole As String = left(sgnfcnd,max(nPwr,0))
      dim sgDecFrac As String = Left(zeroString,max(-nPwr,0))+ mid(sgnfcnd+zeroString,nPwr+1,nMaxDecDigits)
      'Remove trailing zeros
      while right(sgDecFrac,1)="0" and len(sgDecFrac)>(nMinDecDigits)
        sgDecFrac=left(sgDecFrac,len(sgDecFrac)-1)
      wend
      'Add leading zeros to whole part as necessary
      if len(sgWhole)<nLeadingZeros then
        sgWhole=right(Left(zeroString,nLeadingZeros)+sgWhole,nLeadingZeros)
      end if
      Return lftParen+sn+sgWhole+decSepChar+sgDecFrac+rgtParen
    end if
  end if
End Function