我正在尝试使用给定的增量在两个值之间进行插值。
[ 1.0 .. 0.5 .. 20.0 ]
编译器告诉我这已被弃用,并建议使用ints然后转换为float。但是如果我有一个小数增量,这似乎有点啰嗦 - 我是否必须将我的开始值和结束值除以我的增量,然后再将多次除以? (yeuch!)。
我曾经看过一些关于使用序列理解来做这件事的事情,但我不记得是怎么回事。
请帮助。
答案 0 :(得分:9)
TL; DR: F#PowerPack的BigRational
类型是可行的方式。
正如许多人所指出的,float
值不适合循环:
1/3
一样,我们不可避免地会丢失从某个指数开始的所有数字; [0.0000001 .. 0.0000002]
范围内唯一值的数量相当于[1000000 .. 2000000]
中唯一值的数量; 能够立即解决上述问题的是切换回整数逻辑。
使用F# PowerPack,您可以使用BigRational
类型:
open Microsoft.FSharp.Math
// [1 .. 1/3 .. 20]
[1N .. 1N/3N .. 20N]
|> List.map float
|> List.iter (printf "%f; ")
注意,我冒昧地将步骤设置为1/3
,因为您问题中的0.5
实际上具有精确的二进制表示形式0.1 b 并表示为+1.00000000000000000000000 * 2 -1 ;因此它不会产生任何累积求和误差。
输出:
1.000000; 1.333333; 1.666667; 2.000000; 2.333333; 2.666667; 3.000000; (跳过) 18.000000; 18.333333; 18.666667; 19.000000; 19.333333; 19.666667; 20.000000;
// [0.2 .. 0.1 .. 3]
[1N/5N .. 1N/10N .. 3N]
|> List.map float
|> List.iter (printf "%f; ")
输出:
0.200000; 0.300000; 0.400000; 0.500000; (跳过) 2.800000; 2.900000; 3.000000;
BigRational
使用整数计算,不慢而不是浮点数; float
,但不在循环内); BigRational
就像机器epsilon为零一样; 存在一个明显的局限性:您不能使用pi
或sqrt(2)
这样的无理数,因为它们没有精确的表示形式。它似乎不是一个非常大的问题,因为通常情况下,我们并没有遍历两个理性和非理性数字,例如[1 .. pi/2 .. 42]
。如果我们这样做(比如几何计算),通常会有一种减少无理部分的方法,例如:从弧度切换到度数。
进一步阅读:
答案 1 :(得分:5)
有趣的是,浮动范围似乎不再被弃用。我记得最近看到一个问题(对不起,无法追踪),谈论浮动范围表现出来的固有问题,例如。
> let xl = [0.2 .. 0.1 .. 3.0];;
val xl : float list =
[0.2; 0.3; 0.4; 0.5; 0.6; 0.7; 0.8; 0.9; 1.0; 1.1; 1.2; 1.3; 1.4; 1.5; 1.6;
1.7; 1.8; 1.9; 2.0; 2.1; 2.2; 2.3; 2.4; 2.5; 2.6; 2.7; 2.8; 2.9]
我只是想指出,你可以使用decimal
类型的范围,而不是这些舍入问题,例如。
> [0.2m .. 0.1m .. 3.0m];;
val it : decimal list =
[0.2M; 0.3M; 0.4M; 0.5M; 0.6M; 0.7M; 0.8M; 0.9M; 1.0M; 1.1M; 1.2M; 1.3M;
1.4M; 1.5M; 1.6M; 1.7M; 1.8M; 1.9M; 2.0M; 2.1M; 2.2M; 2.3M; 2.4M; 2.5M;
2.6M; 2.7M; 2.8M; 2.9M; 3.0M]
如果你真的确实需要浮点数,那么你可以做类似
的事情> {0.2m .. 0.1m .. 3.0m} |> Seq.map float |> Seq.toList;;
val it : float list =
[0.2; 0.3; 0.4; 0.5; 0.6; 0.7; 0.8; 0.9; 1.0; 1.1; 1.2; 1.3; 1.4; 1.5; 1.6;
1.7; 1.8; 1.9; 2.0; 2.1; 2.2; 2.3; 2.4; 2.5; 2.6; 2.7; 2.8; 2.9; 3.0]
答案 2 :(得分:2)
正如Jon和其他人指出的那样,浮点范围表达式在数值上并不健全。例如,[0.0 .. 0.1 .. 0.3]
等于[0.0 .. 0.1 .. 0.2]
。在范围表达式中使用Decimal或Int Types可能更好。
对于花车我使用此功能,它首先通过最小的浮动步长增加总范围3次。我不确定这个算法现在是否非常强大。但对我来说,确保停止值包含在Seq:
中已经足够了let floatrange start step stop =
if step = 0.0 then failwith "stepsize cannot be zero"
let range = stop - start
|> BitConverter.DoubleToInt64Bits
|> (+) 3L
|> BitConverter.Int64BitsToDouble
let steps = range/step
if steps < 0.0 then failwith "stop value cannot be reached"
let rec frange (start, i, steps) =
seq { if i <= steps then
yield start + i*step
yield! frange (start, (i + 1.0), steps) }
frange (start, 0.0, steps)
答案 3 :(得分:0)
尝试以下序列表达式
seq { 2 .. 40 } |> Seq.map (fun x -> (float x) / 2.0)
答案 4 :(得分:0)
您还可以编写一个相对简单的函数来生成范围:
let rec frange(from:float, by:float, tof:float) =
seq { if (from < tof) then
yield from
yield! frange(from + by, tof) }
使用这个你可以写:
frange(1.0, 0.5, 20.0)
答案 5 :(得分:0)
Tomas Petricek的答案的更新版本,它编译并适用于减少范围(并使用度量单位): (但它看起来不那么漂亮)
let rec frange(from:float<'a>, by:float<'a>, tof:float<'a>) =
// (extra ' here for formatting)
seq {
yield from
if (float by > 0.) then
if (from + by <= tof) then yield! frange(from + by, by, tof)
else
if (from + by >= tof) then yield! frange(from + by, by, tof)
}
#r "FSharp.Powerpack"
open Math.SI
frange(1.0<m>, -0.5<m>, -2.1<m>)
更新我不知道这是新的,还是总是可能的,但我刚刚发现(here),这个 - 更简单 - 语法也是可能的:
let dl = 9.5 / 11.
let min = 21.5 + dl
let max = 40.5 - dl
let a = [ for z in min .. dl .. max -> z ]
let b = a.Length
(注意,在这个特定的例子中有一个问题:)