Quickest way to determine if a number is a prime number or not VB - vb.net

I have been working on this for quite a bit now, I have a task of creating a program which calculates if a number entered by a user is prime number or not, the program calculates the time taken and displays this to the user, however I have found two method, one takes more time than the other but it produce accurate numbers, the other one calculates very quickly however it is wrong, I am hoping if someone can help me and tell me the quickest way of calculating this, here are my two codes
Code1:
Dim ch As String
ch = "y"
While ch = "y"
If (num Mod 2 = 0) Then
Console.WriteLine("Is not a prime number!")
Else
Console.WriteLine("Is a prime number!")
End If
Code2:
check = 1 'initilizing a check point to use it in the program to determine prime number
Dim Value As Long
Console.Write(vbLf & "Enter a number To check Whater it is Prime or Not :")
Value = Long.Parse(Console.ReadLine())
start_time = Now
Dim ch As ULong
ch = 0
Dim i As ULong
i = 2
While (i <= Value / 2)
If (Value Mod i = 0) Then
ch = 1
Exit While
End If
i = i + 1
End While
If (ch = 0) Then
Console.WriteLine("Prime Number")
Else
Console.WriteLine("Not Prime Number")
End If

There are a great many prime testers out there, many of them on this site. For checking a single number I use a faster variant of your Code2 with a little extra checking. Here is the pseudocode:
boolean function isPrime(num)
//1, 0 and negatives cannot be prime.
if (num < 2) then
return false
endif
// 2 is the only even prime.
if (num MOD 2 = 0) then
return (num = 2)
endif
// Check for odd factors.
limit <- sqrt(num)
for (factor <- 3; factor <= limit; factor <- factor + 2) do
if (num MOD factor = 0) then
return false
endif
endfor
// If we reach this point then the number is prime.
return true
endfunction
As #user448810 said, you should use the square root of your target number as the limit of your testing loop. You can basically halve the number of tests you do by treating even numbers separately. Once you have taken out the even numbers, then you only have to test odd factors: 3, 5, 7, ...

Related

Looking for a fast way, to represent the value of an integer, by a number in the range 0 to 3 (without branching?)

Given that val is some random integer,
and number the possible outcome:
if the value is less then &H100 ; the number is 0
if the value is less then &H10000 ; the number is 1
if the value is less then &H1000000 ; the number is 2
else ; the number is 3
I've got this:
If (val And &HFFFF0000) = 0 Then
If (val And &HFF00) = 0 Then
num = 0
Else
numb = 1
End If
ElseIf (**val** And &HFF000000) = 0 Then
numb = 2
Else
numb = 3
End If
I believe to remember that I could achieve this with a simple calculation, but I can not
wrap my head around it...
cheers..
Jhonny
edit:--- after reaction of video.baba ---
Here is half a solution:
The result is a number from 0 to 7, of witch the bits represent a non-zero byte.
A lookuptable could be used to translate it to a 2-bit value.
val >>= 8 ' move to the right, so the first byte can hold identification-bits
val += &H3FF0000 'set a bit in the first byte, if the second one is not zero
val = val And &H400FFFF
val += &H1FFFF00 'set a bit in the first byte, if the third one is not zero
val = val And &H60000FF
val += &HFFFFFF'set a bit in the first byte, if the fourth one is not zero
val >>= 24 'put the result in the last byte
number=lookuptable(val)
have not tested it for speed yet, but it feels over-complicated?
Do you mean something like:
Select Case Value
Case < &H100
Number = 0
Case < &H10000
Number = 1
Case < &H1000000
Number = 2
Case Else
Number = 3
End Select

Program freezes with specific numbers

This code is supposed to take a number and find it's prime factors.
Why does this code work with numbers like 2345 (which returns 5, 7, 67 like its supposed to) but it doesn't work with numbers like 500 and 800?
EDIT: when I say it doesn't work, the program simply does nothing and/or freezes on button click. I then have to stop the program in vb.
Dim number As Double = txtNum.Text
Dim var As Double = 2
Dim result As Double
If number > 1 Then
lst1.Items.Clear()
lst1.Items.Add("The prime factors of " & number & ":")
Do While number > 1
result = number / var
If result = Int(result) Then
lst1.Items.Add(var)
number = result
End If
var = var + 1
Loop
Else
lst1.Items.Clear()
lst1.Items.Add("Let try that again...")
End If
The problem was caused because 500 divided by 2 is 250, divided by 5 is 50 ... and then the program continued on by dividing by 6 instead of attempting to divide by 5 again. Once var reached 10, number became 5 and no further divisions ever allowed it to reach 1 - thus an infinite loop was created.
By changing
If result = Int(result) Then
lst1.Items.Add(var)
number = result
End If
var = var + 1
to
If result = Int(result) Then
lst1.Items.Add(var)
number = result
Else
var = var + 1
End If
it correctly made multiple divisions by the same prime factor when necessary.

VBA: Testing for perfect cubes

I'm trying to write a simple function in VBA that will test a real value and output a string result if it's a perfect cube. Here's my code:
Function PerfectCubeTest(x as Double)
If (x) ^ (1 / 3) = Int(x) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
As you can see, I'm using a simple if statement to test if the cube root of a value is equal to its integer portion (i.e. no remainder). I tried testing the function with some perfect cubes (1, 8, 27, 64, 125), but it only works for the number 1. Any other value spits out the "Flawed" case. Any idea what's wrong here?
You are testing whether the cube is equal to the double supplied.
So for 8 you would be testing whether 2 = 8.
EDIT: Also found a floating point issue. To resolve we will round the decimals a little to try and overcome the issue.
Change to the following:
Function PerfectCubeTest(x As Double)
If Round((x) ^ (1 / 3), 10) = Round((x) ^ (1 / 3), 0) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
Or (Thanks to Ron)
Function PerfectCubeTest(x As Double)
If CDec(x ^ (1 / 3)) = Int(CDec(x ^ (1 / 3))) Then
PerfectCubeTest = "Perfect"
Else
PerfectCubeTest = "Flawed"
End If
End Function
#ScottCraner correctly explains why you were getting incorrect results, but there are a couple other things to point out here. First, I'm assuming that you are taking a Double as input because the range of acceptable numbers is higher. However, by your implied definition of a perfect cube only numbers with an integer cube root (i.e. it would exclude 3.375) need to be evaluated. I'd just test for this up front to allow an early exit.
The next issue you run into is that 1 / 3 can't be represented exactly by a Double. Since you're raising to the inverse power to get your cube root you're also compounding the floating point error. There's a really easy way to avoid this - take the cube root, cube it, and see if it matches the input. You get around the rest of the floating point errors by going back to your definition of a perfect cube as an integer value - just round the cube root to both the next higher and next lower integer before you re-cube it:
Public Function IsPerfectCube(test As Double) As Boolean
'By your definition, no non-integer can be a perfect cube.
Dim rounded As Double
rounded = Fix(test)
If rounded <> test Then Exit Function
Dim cubeRoot As Double
cubeRoot = rounded ^ (1 / 3)
'Round both ways, then test the cube for equity.
If Fix(cubeRoot) ^ 3 = rounded Then
IsPerfectCube = True
ElseIf (Fix(cubeRoot) + 1) ^ 3 = rounded Then
IsPerfectCube = True
End If
End Function
This returned the correct result up to 1E+27 (1 billion cubed) when I tested it. I stopped going higher at that point because the test was taking so long to run and by that point you're probably outside of the range that you would reasonably need it to be accurate.
For fun, here is an implementation of a number-theory based method described here . It defines a Boolean-valued (rather than string-valued) function called PerfectCube() that tests if an integer input (represented as a Long) is a perfect cube. It first runs a quick test which throws away many numbers. If the quick test fails to classify it, it invokes a factoring-based method. Factor the number and check if the multiplicity of each prime factor is a multiple of 3. I could probably optimize this stage by not bothering to find the complete factorization when a bad factor is found, but I had a VBA factoring algorithm already lying around:
Function DigitalRoot(n As Long) As Long
'assumes that n >= 0
Dim sum As Long, digits As String, i As Long
If n < 10 Then
DigitalRoot = n
Exit Function
Else
digits = Trim(Str(n))
For i = 1 To Len(digits)
sum = sum + Mid(digits, i, 1)
Next i
DigitalRoot = DigitalRoot(sum)
End If
End Function
Sub HelperFactor(ByVal n As Long, ByVal p As Long, factors As Collection)
'Takes a passed collection and adds to it an array of the form
'(q,k) where q >= p is the smallest prime divisor of n
'p is assumed to be odd
'The function is called in such a way that
'the first divisor found is automatically prime
Dim q As Long, k As Long
q = p
Do While q <= Sqr(n)
If n Mod q = 0 Then
k = 1
Do While n Mod q ^ k = 0
k = k + 1
Loop
k = k - 1 'went 1 step too far
factors.Add Array(q, k)
n = n / q ^ k
If n > 1 Then HelperFactor n, q + 2, factors
Exit Sub
End If
q = q + 2
Loop
'if we get here then n is prime - add it as a factor
factors.Add Array(n, 1)
End Sub
Function factor(ByVal n As Long) As Collection
Dim factors As New Collection
Dim k As Long
Do While n Mod 2 ^ k = 0
k = k + 1
Loop
k = k - 1
If k > 0 Then
n = n / 2 ^ k
factors.Add Array(2, k)
End If
If n > 1 Then HelperFactor n, 3, factors
Set factor = factors
End Function
Function PerfectCubeByFactors(n As Long) As Boolean
Dim factors As Collection
Dim f As Variant
Set factors = factor(n)
For Each f In factors
If f(1) Mod 3 > 0 Then
PerfectCubeByFactors = False
Exit Function
End If
Next f
'if we get here:
PerfectCubeByFactors = True
End Function
Function PerfectCube(n As Long) As Boolean
Dim d As Long
d = DigitalRoot(n)
If d = 0 Or d = 1 Or d = 8 Or d = 9 Then
PerfectCube = PerfectCubeByFactors(n)
Else
PerfectCube = False
End If
End Function
Fixed the integer division error thanks to #Comintern. Seems to be correct up to 208064 ^ 3 - 2
Function isPerfectCube(n As Double) As Boolean
n = Abs(n)
isPerfectCube = n = Int(n ^ (1 / 3) - (n > 27)) ^ 3
End Function

Recursive function is not following all paths

I've been involved to a challenge.
Here is the question given:
This question involves a game with teddy bears. The game starts when I
give you some bears. You can then give back some bears, but you must
follow these rules (where n is the number of bears that you have):
If n is even, then you may give back exactly n/2 bears. If n is
divisible by 3 or 4, then you may multiply the last two digits of n
and give back this many bears. (By the way, the last digit of n is
n%10, and the next-to-last digit is ((n%100)/10). If n is divisible by
5, then you may give back exactly 42 bears. The goal of the game is to
end up with EXACTLY 42 bears.
For example, suppose that you start with
250 bears. Then you could make these moves:
--Start with 250 bears.
--Since 250 is divisible by 5, you may return 42 of the bears, leaving you with 208 bears.
--Since 208 is even, you may return half of the bears, leaving you with 104 bears.
--Since 104 is even, you may return half of the bears, leaving you with 52 bears.
--Since 52 is divisible by 4, you may multiply the last two digits (resulting in 10) and return these 10 bears. This leaves you with 42
bears.
--You have reached the goal!
Write a recursive function to meet this specification:
bool bears(int n)
// Postcondition: A true return value means that it is possible to win
// the bear game by starting with n bears. A false return value means that
// it is not possible to win the bear game by starting with n bears.
// Examples:
// bear(250) is true (as shown above)
// bear(42) is true
// bear(84) is true
// bear(53) is false
// bear(41) is false
Hint: To test whether n is even, use the expression ((n % 2) == 0).
Here is my solution but unfortinately it always returns false. I guess it is not following the whole alternative paths but have no idea why. Btw, i'm very new with the VB. Thanks in advance.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MsgBox(bear(Int(TextBox1.Text)))
End Sub
Public Function bear(bc As Integer) As Boolean
Dim way1, way2, way3 As Integer
If bc = 42 Then
Return True
ElseIf bc < 42 Then
Return False
ElseIf (bc Mod 2 = 0) Or (bc Mod 3 = 0) Or (bc Mod 4 = 0) Or (bc Mod 5 = 0) Then
If (bc Mod 2 = 0) Then
way1 = bear(bc / 2)
End If
If (bc Mod 3 = 0) Or (bc Mod 4 = 0) Then
way2 = bear((bc Mod 10) * ((bc Mod 100) / 10))
End If
If (bc Mod 5 = 0) Then
way3 = bear(bc - 42)
End If
If (way1 Or way2 Or way3) Then
Return True
Else
Return False
End If
Else
Return False
End If
End Function
(upon further reflection, I can now see that the only problem is the line referenced below..)
.. hang on, it looks like you can do this by just changing one line. In the MOD 3 or 4 case, change this line:
way2 = bear((bc Mod 10) * ((bc Mod 100) / 10))
to these:
dim gb as Integer
gb = (bc Mod 10) * ((bc Mod 100) / 10)
If gb <= 0 then Return False
way2 = bear(bc - gb)
The most obvious thing is that you're checking bears(bearsToTake) instead of bears(bearsLeft-bearsToTake). I think you might be returning false prematurely as well, but I haven't checked, so don't quote me on that.
A solution in Python, for posterity. You don't necessarily need an extra "counter" value like the other answers suggest, but it is often good practice to use one. (I know you're not using Python, but it almost looks like psuedocode, and is thus I've found it easier to grok.)
This solution is almost identical to yours -- it just fixes the parameter from the taken bears to the total bears! bears bears bears.
>>> def checkBears(n):
... if n == 42:
... return True
... elif n < 42:
... return False
... else:
... if not n % 2 and checkBears(n/2):
... return True
... if (not n % 3 or not n % 4) and checkBears(n - n % 10 * (n%100)/10):
... return True
... if not n % 5 and checkBears(n - 42):
... return True
... return False
...
>>> checkBears(250)
True
>>> checkBears(53)
False
>>> checkBears(42)
True
>>> checkBears(84)
True

How can I convert a decimal to a fraction?

How do I convert a indefinite decimal (i.e. .333333333...) to a string fraction representation (i.e. "1/3"). I am using VBA and the following is the code I used (i get an overflow error at the line "b = a Mod b":
Function GetFraction(ByVal Num As Double) As String
If Num = 0# Then
GetFraction = "None"
Else
Dim WholeNumber As Integer
Dim DecimalNumber As Double
Dim Numerator As Double
Dim Denomenator As Double
Dim a, b, t As Double
WholeNumber = Fix(Num)
DecimalNumber = Num - Fix(Num)
Numerator = DecimalNumber * 10 ^ (Len(CStr(DecimalNumber)) - 2)
Denomenator = 10 ^ (Len(CStr(DecimalNumber)) - 2)
If Numerator = 0 Then
GetFraction = WholeNumber
Else
a = Numerator
b = Denomenator
t = 0
While b <> 0
t = b
b = a Mod b
a = t
Wend
If WholeNumber = 0 Then
GetFraction = CStr(Numerator / a) & "/" & CStr(Denomenator / a)
Else
GetFraction = CStr(WholeNumber) & " " & CStr(Numerator / a) & "/" & CStr(Denomenator / a)
End If
End If
End If
End Function
As .333333333 is not 1/3 you will never get 1/3 but instead 333333333/1000000000 if you do not add some clever "un-rounding" logic.
Here is a solution for handling numbers with periodic decimal representation I remember from school.
A number 0.abcdabcd... equals abcd/9999. So 0.23572357... equals 2357/9999 exactly. Just take that many 9s as your pattern is long. 0.11111... equals 1/9, 0.121212... equals 12/99, and so on. So try just searching a pattern and setting the denominator to the corresponding number. Of course you have to stop after some digits because you will never know if the pattern is repeated for ever or just many times. And you will hit the rounding error in the last digit, so you still need some clever logic.
This only works in Excel-VBA but since you had it tagged "VBA" I will suggest it. Excel has a custom "fraction" format that you can access via "Format Cells" (or ctrl-1 if you prefer). This particular number format is Excel-Specific and so does not work with the VBA.Format function. It does however work with the Excel Formula TEXT(). (Which is the Excel equivalent of VBA.Format. This can be accessed like So:
Sub Example()
MsgBox Excel.WorksheetFunction.Text(.3333,"# ?/?")
End Sub
To show more than one digit (Example 5/12) just up the number of question marks.
Google for "decimal to fraction" and you'll get about a gazillion results.
I really like this one, because it's simple, has source code (in RPL, similar to Forth, ~25 lines), and is pretty fast (it's written to run on a 4-bit, 4MHz CPU). The docs say:
In a book called Textbook of Algebra by G. Chrystal, 1st
edition in 1889, in Part II, Chapter 32, this improved continued fraction
algorithm is presented and proven. Odd to tell, Chrystal speaks of it as if it
were ancient knowledge.
This site seem to have a really nice implementation of this in JavaScript.
I would multiply by 10000000(or whatever you want depending on the precision), then simplify the resulting fraction (ie n*10000000/10000000)
You can approximate it. Essentially cycle through all numerators and denominators until you reach a fraction that is close to what you want.
int num = 1;
int den = 1;
double limit == 0.1;
double fraction = num / den;
while(den < 1000000 ) // some arbitrary large denominator
{
den = den + 1;
for(num = 0; num <= den; num++)
{
fraction = num / den;
if(fraction < n + limit && fraction > n - limit)
return (num + "/" + den);
}
}
This is slow and a brute force algorithm, but you should get the general idea.
In general, it'll be easier if you find the repeating part of the rational number. If you can't find that, you'll have a tough time. Let's say the number if 8.45735735735...
The answer is 8 + 45/100 + 735/999/100 = 8 1523/3330.
The whole number is 8.
Add 45/100 - which is .45, the part before the repeating part.
The repeating part is 735/999. In general, take the repeating part. Make it the numerator. The denominator is 10^(number of repeating digits) - 1.
Take the repeating part and shift it the appropriate number of digits. In this case, two, which means divide by 100, so 735/999/100.
Once you figure those parts out, you just need some code that adds and reduces fractions using greatest common fractions ...
Similar to CookieOfFortune's, but it's in VB and doesn't use as much brute force.
Dim tolerance As Double = 0.1 'Fraction has to be at least this close'
Dim decimalValue As Double = 0.125 'Original value to convert'
Dim highestDenominator = 100 'Highest denominator you`re willing to accept'
For denominator As Integer = 2 To highestDenominator - 1
'Find the closest numerator'
Dim numerator As Integer = Math.Round(denominator * decimalValue)
'Check if the fraction`s close enough'
If Abs(numerator / denominator - decimalValue) <= tolerance Then
Return numerator & "/" & denominator
End If
Next
'Didn't find one. Use the highest possible denominator'
Return Math.Round(denominator * decimalValue) & "/" & highestDenominator
...Let me know if it needs to account for values greater than 1, and I can adjust it.
EDIT: Sorry for the goofed up syntax highlighting. I can't figure out why it's all wrong. If someone knows how I can make it better, please let me know.
Python has a nice routine in its fractions module. Here is the working portion that converts a n/d into the closest approximation N/D where D <= some maximum value. e.g. if you want to find the closest fraction to 0.347, let n=347,d=1000 and max_denominator be 100 and you will obtain (17, 49) which is as close as you can get for denominators less than or equal to 100. The '//' operator is integer division so that 2//3 gives 0, i.e. a//b = int(a/b).
def approxFrac(n,d,max_denominator):
#give a representation of n/d as N/D where D<=max_denominator
#from python 2.6 fractions.py
#
# reduce by gcd and only run algorithm if d>maxdenominator
g, b = n, d
while b:
g, b = b, g%b
n, d = n/g, d/g
if d <= max_denominator:
return (n,d)
nn, dd = n, d
p0, q0, p1, q1 = 0, 1, 1, 0
while True:
a = nn//dd
q2 = q0+a*q1
if q2 > max_denominator:
break
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
nn, dd = dd, nn-a*dd
k = (max_denominator-q0)//q1
bound1 = (p0+k*p1, q0+k*q1)
bound2 = (p1, q1)
if abs(bound2[0]*d - bound2[1]*n) <= abs(bound1[0]*d - bound1[1]*n):
return bound2
else:
return bound1
1/ .3333333333 = 3 because 1/3 = .3333333333333, so whatever number you get do this,
double x = 1 / yourDecimal;
int y = Math.Ceil(x);
and now Display "1/" + y
It is not allways resoluble, since not all decimals are fractions (for example PI or e).
Also, you have to round up to some length your decimal before converting.
I know this is an old thread, but I came across this problem in Word VBA. There are so many limitations due to the 8 bit (16 digit) rounding, as well as Word VBA making decimals into scientific notation etc.. but after working around all these problems, I have a nice function I'd like to share that offers a few extra features you may find helpful.
The strategy is along the lines of what Daniel Buckner wrote. Basically:
1st) decide if it's a terminating decimal or not
2nd) If yes, just set the decimal tail / 10^n and reduce the fraction.
3rd) If it doesn't terminate, try to find a repeating pattern including cases where the repetition doesn't start right away
Before I post the function, here are a few of my observations of the risks and limitations, as well as some notes that may help you understand my approach.
Risks, limitations, explanations:
-> Optional parameter "denom" allows you to specify the denominator of the fraction, if you'd like it rounded. i.e. for inches you may want 16ths used. The fractions will still be reduced, however, so 3.746 --> 3 12/16 --> 3 3/4
-> Optional parameter "buildup" set to True will build up the fraction using the equation editor, typing the text right into the active document. If you prefer to have the function simply return a flat string representation of the fraction so you can store it programmatically etc. set this to False.
-> A decimal could terminate after a bunch of repetitions... this function would assume an infinite repetition.
-> Variable type Double trades off whole number digit for decimal digits, only allowing 16 digits total (from my observations anyway!). This function assumes that if a number is using all 16 of the available digits then it must be a repeating decimal. A large number such as 123456789876.25 would be mistaken for a repeating decimal, then returned as decimal number upon failing to find a pattern.
-> To express really large terminating decimal out of 10^n, VB can only handle 10^8 is seems. I round the origninal number to 8 decimal places, losing some accuracy perhaps.
-> For the math behind converting repeating patterns to fractions check this link
-> Use Euclidean Algorithm to reduce the fraction
Ok, here it is, written as a Word Macro:
Function as_fraction(number_, Optional denom As Integer = -1, Optional buildup As Boolean = True) As String
'Selection.TypeText Text:="Received: " & CStr(number_) & vbCrLf
Dim number As Double
Dim repeat_digits As Integer, delay_digits As Integer, E_position As Integer, exponent As Integer
Dim tail_string_test As String, tail_string_original As String, num_removed As String, tail_string_removed As String, removed As String, num As String, output As String
output = "" 'string variable to build into the fraction answer
number = CDbl(number_)
'Get rid of scientific notation since this makes the string longer, fooling the function length = digits
If InStr(CStr(number_), "E+") > 0 Then 'no gigantic numbers! Return that scientific notation junk
output = CStr(number_)
GoTo all_done
End If
E_position = InStr(CStr(number), "E") 'E- since postives were handled
If E_position > 0 Then
exponent = Abs(CInt(Mid(CStr(number), E_position + 1)))
num = Mid(CStr(number_), 1, E_position) 'axe the exponent
decimalposition = InStr(num, ".") 'note the decimal position
For i_move = 1 To exponent
'move the decimal over, and insert a zero if the start of the number is reached
If InStr(num, "-") > 0 And decimalposition = 3 Then 'negative sign in front
num = "-0." & Mid(num, InStr(num, ".") - 1, 1) & Mid(num, InStr(num, ".") + 1) 'insert a zero after the negative
ElseIf decimalposition = 2 Then
num = "0." & Mid(num, InStr(num, ".") - 1, 1) & Mid(num, InStr(num, ".") + 1) 'insert in front
Else 'move the decimal over, there are digits left
num = Mid(num, 1, decimalposition - 2) & "." & Mid(num, decimalposition - 1, 1) & Mid(num, decimalposition + 1)
decimalposition = decimalposition - 1
End If
Next
Else
num = CStr(number_)
End If
'trim the digits to 15, since VB rounds the last digit which ruins the pattern. i.e. 0.5555555555555556 etc.
If Len(num) >= 16 Then
num = Mid(num, 1, 15)
End If
number = CDbl(num) 'num is a string representation of the decimal number, just to avoid cstr() everywhere
'Selection.TypeText Text:="number = " & CStr(number) & vbCrLf
'is it a whole number?
If Fix(number) = number Then 'whole number
output = CStr(number)
GoTo all_done
End If
decimalposition = InStr(CStr(num), ".")
'Selection.TypeText Text:="Attempting to find a fraction equivalent for " & num & vbCrLf
'is it a repeating decimal? It will have 16 digits
If denom = -1 And Len(num) >= 15 Then 'repeating decimal, unspecified denominator
tail_string_original = Mid(num, decimalposition + 1) 'digits after the decimal
delay_digits = -1 'the number of decimal place values removed from the tail, in case the repetition is delayed. i.e. 0.567777777...
Do 'loop through start points for the repeating digits
delay_digits = delay_digits + 1
If delay_digits >= Fix(Len(tail_string_original) / 2) Then
'Selection.TypeText Text:="Tried all starting points for the pattern, up to half way through the tail. None was found. I'll treat it as a terminating decimal." & vbCrLf
GoTo treat_as_terminating
End If
num_removed = Mid(num, 1, decimalposition) & Mid(num, decimalposition + 1 + delay_digits) 'original number with decimal values removed
tail_string_removed = Mid(num_removed, InStr(CStr(num_removed), ".") + 1)
repeat_digits = 0 'exponent on 10 for moving the decimal place over
'Selection.TypeText Text:="Searching " & num_removed & " for a pattern:" & vbCrLf
Do
repeat_digits = repeat_digits + 1
If repeat_digits = Len(tail_string_removed) - 1 Or repeat_digits >= 9 Then 'try removing a digit, incase the pattern is delayed
Exit Do
End If
tail_string_test = Mid(num_removed, decimalposition + 1 + repeat_digits)
'Selection.TypeText Text:=vbTab & "Comparing " & Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) & " to " & tail_string_test & vbCrLf
If Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) = tail_string_test Then
'Selection.TypeText Text:=num & ", " & Mid(tail_string_removed, 1, Len(tail_string_removed) - repeat_digits) & " vs " & tail_string_test & vbCrLf
GoTo foundpattern
End If
Loop
Loop 'next starting point for pattern
foundpattern:
If delay_digits = 0 Then 'found pattern right away
numerator = CLng(Mid(CStr(number), decimalposition + 1 + delay_digits, CInt(repeat_digits)))
'generate the denominator nines, same number of digits as the numerator
bottom = ""
For i_loop = 1 To repeat_digits
bottom = bottom & "9"
Next
denominator = CLng(bottom)
Else 'there were numbers before the pattern began
numerator = CLng(Mid(num, decimalposition + 1, delay_digits + repeat_digits)) - CLng(Mid(num, decimalposition + 1, delay_digits))
'i.e. x = 2.73232323232... delay_digits = 1, repeat_digits = 2, so numerator = 732 - 7 = 725
bottom = ""
For i_loop = 1 To repeat_digits
bottom = bottom & "9"
Next
For i_loop = 1 To delay_digits
bottom = bottom & "0"
Next
denominator = CLng(bottom)
'i.e. 990... 725/990 = 145/198 = 0.7323232...
End If
Else ' terminating decimal
treat_as_terminating:
'grab just the decimal trail
If denom = -1 Then
number = Math.Round(number, 8) 'reduce to fewer decimal places to avoid overload
'is it a whole number now?
If Fix(number) = number Then 'whole number
output = CStr(number)
GoTo all_done
End If
num = CStr(number)
numerator = CLng(Mid(num, decimalposition + 1))
denominator = 10 ^ (Len(num) - InStr(num, "."))
Else 'express as a fraction rounded to the nearest denom'th reduced
numerator1 = CDbl("0" & Mid(CStr(num), decimalposition))
numerator = CInt(Math.Round(numerator1 * denom))
denominator = CInt(denom)
End If
End If
'reduce the fraction if possible using Euclidean Algorithm
a = CLng(numerator)
b = CLng(denominator)
Dim t As Long
Do While b <> 0
t = b
b = a Mod b
a = t
Loop
gcd_ = a
numerator = numerator / gcd_
denominator = denominator / gcd_
whole_part = CLng(Mid(num, 1, decimalposition - 1))
'only write a whole number if the number is absolutely greater than zero, or will round to be so.
If whole_part <> 0 Or (whole_part = 0 And numerator = denominator) Then
'case where fraction rounds to whole
If numerator = denominator Then
'increase the whole by 1 absolutely
whole_part = (whole_part / Abs(whole_part)) * (Abs(whole_part) + 1)
End If
output = CStr(whole_part) & " "
End If
'if fraction rounded to a whole, it is already included in the whole number
If numerator <> 0 And numerator <> denominator Then
'negative sign may have been missed, if whole number was -0
If whole_part = 0 And number_ < 0 Then
numerator = -numerator
End If
output = output & CStr(numerator) & "/" & CStr(denominator) & " "
End If
If whole_part = 0 And numerator = 0 Then
output = "0"
End If
all_done:
If buildup = True Then 'build up the equation with a pretty fraction at the current selection range
Dim objRange As Range
Dim objEq As OMath
Dim AC As OMathAutoCorrectEntry
Application.OMathAutoCorrect.UseOutsideOMath = True
Set objRange = Selection.Range
objRange.Text = output
For Each AC In Application.OMathAutoCorrect.Entries
With objRange
If InStr(.Text, AC.Name) > 0 Then
.Text = Replace(.Text, AC.Name, AC.Value)
End If
End With
Next AC
Set objRange = Selection.OMaths.Add(objRange)
Set objEq = objRange.OMaths(1)
objEq.buildup
'Place the cursor at the end of the equation, outside of the OMaths object
objRange.OMaths(1).Range.Select
Selection.Collapse direction:=wdCollapseEnd
Selection.MoveRight Unit:=wdCharacter, count:=1
as_fraction = "" 'just a dummy return to make the function happy
Else 'just return a flat string value
as_fraction = output
End If
End Function
I shared an answer at this link : https://stackoverflow.com/a/57517128/11933717
It's also an iterative function, but unlike finding numerator and denominator in a nested loop, it just tests numerators only and so, should be faster.
Here is how it works :
It assumes that, based on the user input x, you want to find 2 integers n / m .
n/m = x , meaning that
n/x should give an almost integer m
Say one needs to find a fraction for x = 2.428571. Putting the int 2 aside for later, the algo starts by setting n and x and iterates n :
// n / x = m ( we need m to be an integer )
// n = 1 ; x = .428571 ;
1 / .428571 = 2.333335 (not close to an integer, n++)
2 / .428571 = 4.666671 (not close to an integer, n++)
3 / .428571 = 7.000007
At this point n = 3, we consider that m = 7.000007 is integer enough --based on some kind of accuracy the programmer decides-- and we reply the user
2.428571 = 2 + 3/7
= 14/7 + 3/7
= 17/7