如何将不定小数(即.333333333 ...)转换为字符串分数表示(即“1/3”)。我正在使用VBA,以下是我使用的代码(我在“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
答案 0 :(得分:18)
如果你没有添加一些聪明的“非舍入”逻辑,那么.333333333不是1/3,你将永远不会得到1/3而是333333333/1000000000。
这是一个处理数字的解决方案,我记得从学校开始,它是周期性的十进制表示法。
数字0. abcd abcd ...等于abcd / 9999。所以0. 2357 2357 ......完全等于2357/9999。因为你的模式很长,所以要花很多9个。 0. 1 1111 ...等于1 / 9,0 12 1212 ...等于12/99,依此类推。因此,尝试只搜索模式并将分母设置为相应的数字。当然,你必须在一些数字之后停下来,因为你永远不会知道这个模式是永久重复还是只重复多次。并且您将在最后一位数字中遇到舍入误差,因此您仍需要一些聪明的逻辑。
答案 1 :(得分:6)
这仅适用于Excel-VBA,但由于您将其标记为“VBA”,我会建议它。 Excel具有自定义“分数”格式,您可以通过“格式化单元格”(或者如果您愿意,可以使用ctrl-1)访问该格式。此特定数字格式是Excel特定的,因此不适用于VBA.Format函数。它 然后使用Excel公式TEXT()。 (这与Excel相当于VBA.Format。可以像So:
那样访问Sub Example()
MsgBox Excel.WorksheetFunction.Text(.3333,"# ?/?")
End Sub
要显示多个数字(例5/12),只需显示问号数。
答案 2 :(得分:2)
谷歌的“十进制分数”,你将得到大量的结果。
我真的很喜欢this one,因为它很简单,有源代码(在RPL中,类似于Forth,~25行),并且非常快(它编写为在4位,4MHz CPU上运行) 。文档说:
由G. Chrystal编写的一本名为代数教科书的书 在1889年的第二部分第32章中,这改进了连续分数 算法被提出并证明。奇怪的是,Chrystal说它就像它一样 是古老的知识。
答案 3 :(得分:2)
This site似乎在JavaScript中有一个很好的实现。
答案 4 :(得分:1)
我会乘以10000000(或任何你想要的,具体取决于精度),然后简化得到的分数(即n * 10000000/10000000)
答案 5 :(得分:1)
你可以近似它。基本上循环遍历所有分子和分母,直到达到接近你想要的分数。
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);
}
}
这很慢并且是一种蛮力算法,但你应该得到一般的想法。
答案 6 :(得分:1)
一般来说,如果找到有理数的重复部分会更容易。如果你找不到,那你将度过难关。让我们说一下如果是8.45735735735 ...
答案是8 + 45/100 + 735/999/100 = 8 1523/3330。
整数是8。 添加45/100 - 这是.45,重复部分之前的部分。
重复部分是735/999。一般来说,采取重复的部分。把它作为分子。分母是10 ^(重复数字的数量) - 1.
取重复部分并将其移动适当的位数。在这种情况下,两个,即除以100,即735/999/100。
一旦你想出这些部分,你只需要一些代码,使用最常见的分数来增加和减少分数......
答案 7 :(得分:1)
与CookieOfFortune类似,但它在VB中并没有使用那么多暴力。
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
...如果需要考虑大于1的值,请告诉我,我可以调整它。
编辑:抱歉,语法高亮显示。我无法弄清楚为什么一切都错了。如果有人知道如何让它变得更好,请告诉我。
答案 8 :(得分:1)
Python在其分数模块中有一个很好的例程。这是将n / d转换为最接近的近似N / D的工作部分,其中D <=某个最大值。例如如果你想找到最接近0.347的分数,设n = 347,d = 1000,max_denominator为100,你将获得(17,49),它与你的分母小于或等于100的距离最近。 '//'运算符是整数除法,因此2 // 3给出0,即// 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
答案 9 :(得分:0)
1 / .3333333333 = 3因为1/3 = .3333333333333,所以无论你得到什么数字,
double x = 1 / yourDecimal; int y = Math.Ceil(x);
现在显示“1 /”+ y
答案 10 :(得分:0)
它并不总是可溶的,因为并非所有小数都是分数(例如PI或e)。
另外,在转换之前,你必须将你的小数舍入到一定长度。
答案 11 :(得分:0)
我知道这是一个旧线程,但我在Word VBA中遇到了这个问题。由于8位(16位)舍入,以及Word VBA将小数写入科学记数法等,因此有很多限制。但在解决了所有这些问题后,我有一个很好的功能我想分享它提供了一些您可能会发现有用的额外功能。
该战略与丹尼尔·巴克纳所写的一致。基本上: 1)确定它是否是终止小数 2)如果是,只需设置小数尾/ 10 ^ n并减少分数 3)如果它没有终止,试着找一个重复模式,包括重复不立即开始的情况
在发布该功能之前,以下是我对风险和限制的一些观察,以及可能有助于您理解我的方法的一些注释。
风险,限制,解释:
- &GT;可选参数&#34; denom&#34;允许您指定分数的分母,如果您喜欢它四舍五入。即对于英寸,你可能需要使用16。然而,分数仍将减少,因此3.746 - > 3 12/16 - &gt; 3 3/4
- &GT;可选参数&#34; builddup&#34;设置为True将使用公式编辑器构建分数,在活动文档中键入文本。如果您希望函数只返回分数的扁平字符串表示,那么您可以以编程方式存储它等。将此设置为False。
- &GT;十进制可以在一堆重复之后终止......这个函数将假定无限重复。
- &GT;变量类型Double换掉十进制数字的整数位数,总共只允许16位数(从我的观察结果来看!)。此函数假定如果一个数字使用所有16个可用数字,则它必须是重复小数。如123456789876.25这样的大数字会被误认为是重复的小数,然后在找不到模式时以十进制数的形式返回。
- &GT;为了表达10 ^ n中真正大的终止小数,VB似乎只能处理10 ^ 8。我将origninal数字四舍五入到小数点后8位,可能会失去一些准确性。
- &GT;对于将重复模式转换为分数的数学运算,请检查this link
- &GT;使用Euclidean Algorithm减少分数
好的,这里写的是Word宏:
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
答案 12 :(得分:0)
我在此链接上分享了一个答案:https://stackoverflow.com/a/57517128/11933717
它也是一个迭代函数,但是与在嵌套循环中查找分子和分母不同,它仅测试分子,因此应该更快。
这是它的工作方式:
它假定基于用户输入x,您想要找到2个整数n / m。
说一个需要找到x = 2.428571的分数。将int 2放置以备后用,算法将从设置n和x开始并迭代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
在此时n = 3,我们认为m = 7.000007是整数足够了-基于程序员决定的某种精度-并回复用户
2.428571 = 2 + 3/7
= 14/7 + 3/7
= 17/7
答案 13 :(得分:-1)
你试过这个routine吗?