找到分数lua时出错。

huangapple go评论72阅读模式
英文:

an error while trying to find fraction lua

问题

以下是代码的翻译部分:

local function FindFraction(float)
    local i, f = math.modf(float)
    local result = 1 / f % 1 == 0 and '1 / ' .. math.floor(1 / f) or f == 0 and i or nil
    if not result then
        for k = 1, 10000 do
            if 1 / f * k % 1 == 0 then
                result = tostring(k + i .. ' / ' .. 1 / f * k)
                break
            end
        end
    end
    return float > 1 and i .. ' + (' .. result .. ')' or result
end
print(FindFraction(0.1)) -- 1 / 10
print(FindFraction(0.2)) -- 1 / 5
print(FindFraction(0.3)) -- 3 / 10
print(FindFraction(0.4)) -- 2 / 5
print(FindFraction(0.5)) -- 1 / 2
print(FindFraction(0.6)) -- 3 / 5
print(FindFraction(0.7)) -- 7 / 10
print(FindFraction(0.8)) -- 8 / 10
print(FindFraction(0.9)) -- 9 / 10
print(FindFraction(1)) -- 1
print(FindFraction(1.1)) -- nil or error (这里有问题,有什么错?)

希望这有帮助。

英文:
local function FindFraction(float)
    local i,f = math.modf(float)
    local result = 1 / f % 1 == 0 and '1 / ' .. math.floor(1 / f) or f == 0 and i or nil
    if not result then 
        for k = 1, 10000 do 
            if 1 / f * k % 1 == 0 then 
                result = tostring(k + i .. ' / ' .. 1 / f * k)
                break 
            end
        end 
    end
    return float > 1 and i .. ' + (' .. result .. ')' or result
end 
print(FindFraction(0.1)) -- 1 / 10
print(FindFraction(0.2)) -- 1 / 5
print(FindFraction(0.3)) -- 3 / 10
print(FindFraction(0.4)) -- 2 / 5
print(FindFraction(0.5)) -- 1 / 2
print(FindFraction(0.6)) -- 3 / 5
print(FindFraction(0.7)) -- 7 / 10 
print(FindFraction(0.8)) -- 8 / 10 
print(FindFraction(0.9)) -- 9 / 10 
print(FindFraction(1)) -- 1
print(FindFraction(1.1)) -- nil or error ( what is wrong here? )

it works well sometimes like .234 is 117 / 500 but sometimes it straight errors because the result is nil

tried everything i could, nothing helped

Edit: thanks guys i just found out the problem and solved it by adding
f = tonumber(string.format('%g',string.format('%.'..(10)..'f',f)))
on the 3rd line!

答案1

得分: 1

Ivo解释了问题源于浮点精度问题:像1.1这样的数字并不精确地存储为(分数)11/10。您可以通过使用比Lua默认使用的更多精度位数的格式进行格式化来看到舍入误差:

> ("%.17g"):format(1.1)
1.1000000000000001

然而,在底层,浮点数实际上只是具有固定分母的分数。我们可以使用math.frexp从浮点数中提取尾数和指数:

> 返回m和e,使得x = m2^e,e是整数,而m的绝对值在范围[0.5,1)内(或者当x为零时为零)。

这使我们能够提取指数,剩下尾数。64位浮点数的尾数有52位加上一个隐式位。因此,如果我们乘以2^53,我们保证得到一个整数。由于这只是与二的幂相乘,它在底层所做的只是增加指数。

local function FindFraction(float)
	local m, e = math.frexp(float)
	local denom = 2^53
	local num = m * denom
	if e < 0 then
		denom = denom * 2^-e
	else
		num = num * 2^e
	end
	return ("%d/%d"):format(num, denom)
end

for _, float in ipairs{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1} do
	print(float, FindFraction(float))
end

这会生成丑陋但准确表示浮点数的分数:

0.1	7205759403792794/72057594037927936
0.2	7205759403792794/36028797018963968
0.3	5404319552844595/18014398509481984
0.4	7205759403792794/18014398509481984
0.5	4503599627370496/9007199254740992
0.6	5404319552844595/9007199254740992
0.7	6305039478318694/9007199254740992
0.8	7205759403792794/9007199254740992
0.9	8106479329266893/9007199254740992
1	9007199254740992/9007199254740992
1.1	9907919180215092/9007199254740992

目前,这并不会尝试通过将分子和分母都除以两者的最大公约数(GCD)来缩短所得到的分数;也许您想查看我为此编写的Lua 分数实现。请注意,最终您将得到使用分子和分母的分数,这些分子和分母可能不能失去精度。如果您希望分数运算始终精确工作,您将不得不使用大整数作为分子和分母。

英文:

Ivo explained how the problem stems from float precision issues: A number like 1.1 isn't stored as exactly (the fraction) 11/10. You can see rounding errors by formatting using more digits of precision than Lua uses by default:

> ("%.17g"):format(1.1)
1.1000000000000001

However, under the hood, floats are effectively just fractions with a fixed denominator. We can extract mantissa and exponent from floats using math.frexp:

> Returns m and e such that x = m2^e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero).

This allows us to extract the exponent, leaving us with the mantissa. The mantissa of a 64-bit float has 52 bits plus one implicit bit. Thus if we multiply by 2^53, we are guaranteed to get an integer. Since this is just a multiplication with a power of two, all it does under the hood is increment the exponent.

local function FindFraction(float)
	local m, e = math.frexp(float)
	local denom = 2^53
	local num = m * denom
	if e < 0 then
		denom = denom * 2^-e
	else
		num = num * 2^e
	end
	return ("%d/%d"):format(num, denom)
end

for _, float in ipairs{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.1} do
	print(float, FindFraction(float))
end

This produces ugly, but accurate fractions representing the floats:

0.1	7205759403792794/72057594037927936
0.2	7205759403792794/36028797018963968
0.3	5404319552844595/18014398509481984
0.4	7205759403792794/18014398509481984
0.5	4503599627370496/9007199254740992
0.6	5404319552844595/9007199254740992
0.7	6305039478318694/9007199254740992
0.8	7205759403792794/9007199254740992
0.9	8106479329266893/9007199254740992
1	9007199254740992/9007199254740992
1.1	9907919180215092/9007199254740992

This currently makes no effort to shorten the resulting fractions by dividing both numerator and denominator by the GCD of the two; perhaps you will want to take a look at this Lua fraction implementation I wrote for that. Note that you will ultimately end up with fractions which use numerators and denominators which may not afford to lose precision. If you want fraction operations to always work exactly, you'll have to use big integers for numerator and denominator.

答案2

得分: 0

问题在于

if 1 / f * k % 1 == 0 then

在这种情况下对所有的 k 都返回false。

错误是由于浮点精度问题引起的。考虑以下代码:

local i,f = math.modf(1.1)
print(i,f)
print(f - 0.1)

第一个打印结果如预期:

1	0.1

但第二个打印并不返回0,而实际上是:

8.3266726846887e-17

我不确定你如何解决这个问题,但也许这可以指导你朝着正确的方向思考。

英文:

The problem is that

if 1 / f * k % 1 == 0 then

gives false for all k in this situation.

The error is because of floating point precision. Consider this code:

local i,f = math.modf(1.1)
print(i,f)
print(f - 0.1)

This first print gives as expected

1	0.1

But the second print does not return 0 but actually

8.3266726846887e-17

I'm not sure how you could solve this but maybe this points you in the right direction.

答案3

得分: 0

大家,我重写了代码,现在它可以处理最多6位整数和小数了。
以下是代码:

local function FindFraction(float)
    local int, float = math.modf(float)
    if float == 0 then return int end
    float = string.format('%g', float)
    local function ReduceFraction(numerator, denominator)
        local n, m = numerator, denominator
        while m ~= 0 do 
            n, m = m, n % m 
        end 
        return numerator / n .. ' / ' .. denominator / n 
    end
    local digits = float:sub(float:find('.') + 2, #float)
    local numerator, denominator = tonumber(digits), 10 ^ #digits
    return int > 1 and int .. ' + (' .. ReduceFraction(numerator, denominator) .. ')' or ReduceFraction(numerator, denominator) 
end
英文:

guys i rewrote the code and now it works well with everything up to 6 digits and integers
here it is:

local function FindFraction(float)
    local int, float = math.modf(float)
    if float == 0 then return int end
    float = string.format('%g',float)
    local function ReduceFraction(numerator,denominator)
        local n, m = numerator, denominator
        while m ~= 0 do 
            n, m = m, n % m 
        end 
        return numerator/n .. ' / ' .. denominator/n 
    end
    local digits = float:sub(float:find('.')+2,#float)
    local numerator, denominator = tonumber(digits), 10 ^ #digits
    return int > 1 and int .. ' + ('..ReduceFraction(numerator,denominator)..')' or ReduceFraction(numerator,denominator) 
end

huangapple
  • 本文由 发表于 2023年8月10日 19:48:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/76875459.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定