问题描述
我正在尝试制作一个小函数,以给定增量在两个值之间进行插值.
I'm trying to make a little function to interpolate between two values with a given increment.
[ 1.0 .. 0.5 .. 20.0 ]
编译器告诉我这已被弃用,并建议使用整数然后转换为浮点数.但是如果我有一个小数增量,这似乎有点冗长 - 我是否必须将我的开始和结束值除以我的增量,然后再乘以?(耶!).
The compiler tells me that this is deprecated, and suggests using ints then casting to float. But this seems a bit long-winded if I have a fractional increment - do I have to divide my start and end values by my increment, then multiple again afterwards? (yeuch!).
我曾在某处看到过有关使用序列推导式执行此操作的内容,但我不记得是如何做到的.
I saw something somewhere once about using sequence comprehensions to do this, but I can't remember how.
请帮忙.
推荐答案
TL;DR:F# PowerPack 的 BigRational
类型是要走的路.
TL;DR: F# PowerPack's BigRational
type is the way to go.
正如许多人指出的那样,float
值不适合循环:
As many have pointed out, float
values are not suitable for looping:
- 它们确实有舍入误差,就像十进制的
1/3
一样,我们不可避免地会丢失从某个指数开始的所有数字; - 他们确实经历了灾难性取消(当减去两个几乎相等的数字时,结果四舍五入为零);
- 他们总是有非零的机器 epsilon,所以误差会增加数学运算(除非我们多次添加不同的数字,以便错误相互抵消——但循环并非如此);
- 它们在整个范围内确实具有不同的准确度:范围
[0.0000001 .. 0.0000002]
中唯一值的数量等于[1000000 .. 2000000 中唯一值的数量]
;
- They do have Round Off Error, just like with
1/3
in decimal, we inevitably lose all digits starting at a certain exponent; - They do experience Catastrophic Cancellation (when subtracting two almost equal numbers, the result is rounded to zero);
- They always have non-zero Machine epsilon, so the error is increased with every math operation (unless we are adding different numbers many times so that errors mutually cancel out -- but this is not the case for the loops);
- They do have different accuracy across the range: the number of unique values in a range
[0.0000001 .. 0.0000002]
is equivalent to the number of unique values in[1000000 .. 2000000]
;
可以立即解决上述问题的是切换回整数逻辑.
What can instantly solve the above problems, is switching back to integer logic.
使用 F# PowerPack,您可以使用 BigRational
类型:
With F# PowerPack, you may use BigRational
type:
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 表示为 +1.00000000000000000000000 * 2;因此它不会产生任何累积求和误差.
Note, I took my liberty to set the step to 1/3
because 0.5
from your question actually has an exact binary representation 0.1 and is represented as +1.00000000000000000000000 * 2; hence it does not produce any cumulative summation error.
输出:
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 为零一样;BigRational
uses integer computations, which are not slower than for floating-points;- The round-off occurs only once for each value (upon conversion to a
float
, but not within the loop); BigRational
acts as if the machine epsilon were zero;
Conclusion
有一个明显的限制:你不能使用像 pi
或 sqrt(2)
这样的无理数,因为它们没有精确的分数表示.这似乎不是一个很大的问题,因为通常,我们不会遍历有理数和无理数,例如[1 .. pi/2 .. 42]
.如果我们这样做(例如几何计算),通常有一种方法可以减少非理性部分,例如从弧度切换到度数.
There is an obvious limitation: you can't use irrational numbers like pi
or sqrt(2)
as they have no exact representation as a fraction. It does not seem to be a very big problem because usually, we are not looping over both rational and irrational numbers, e.g. [1 .. pi/2 .. 42]
. If we do (like for geometry computations), there's usually a way to reduce the irrational part, e.g. switching from radians to degrees.
进一步阅读:
这篇关于F# 浮点范围是实验性的,可能会被弃用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!