Math 对浮体施加(某些情况下)周期性边界条件的有效方法?
通过简单地执行以下操作,可以非常有效地对整数施加某些情况:Math 对浮体施加(某些情况下)周期性边界条件的有效方法?,math,graphics,floating-point,ieee-754,Math,Graphics,Floating Point,Ieee 754,通过简单地执行以下操作,可以非常有效地对整数施加某些情况: myWrappedWithinPeriodicBoundary = myUIntValue & mask 当边界为半开范围[0,上限]时,此选项有效,(独占)上限为2^exp,以便 mask = (1 << exp) - 1 问题:是否有任何(大致类似的)有效方法将PBC(某些情况下)强加于浮点数(32或64位IEEE-754)?有几种合理的方法: -具有静态知道其除数的优势,但在我的测试中,它来自libc.s
myWrappedWithinPeriodicBoundary = myUIntValue & mask
当边界为半开范围[0,上限]时,此选项有效,(独占)上限为2^exp,以便
mask = (1 << exp) - 1
问题:是否有任何(大致类似的)有效方法将PBC(某些情况下)强加于浮点数(32或64位IEEE-754)?有几种合理的方法:
libc.so.6
,即使使用-ffast math
floor的手动位旋转实现
floor
)关闭。直接实现可以使用浮点除法或移位来手动重新组装双精度
;即使给定硬件,地板
实现也可以立即重建双精度
:地板
从不更改其参数的指数nt,除非它返回0。大约需要20行C
下面的计时是使用double
和gcc-O3
,在代表性输入上使用计时循环,操作代码被内联到这些输入上
fmod: 41.8 ns
modf: 19.6 ns
floor: 10.6 ns
使用-ffast数学:
fmod: 26.2 ns
modf: 30.0 ns
floor: 21.9 ns
位操作:
direct: 18.0 ns
floor: 20.6 ns
手动实现是有竞争力的,但是
floor
技术是最好的。奇怪的是,三个库函数中有两个在没有-ffast math
的情况下性能更好:也就是说,作为PLT函数调用比作为内联内置函数更好。我将这个答案添加到我自己的问题中,因为它描述了ing,这是我找到的最好的解决方案。它在Swift 4.1中(应该直接翻译成C),并且已经在各种用例中进行了测试:
extension BinaryFloatingPoint {
/// Returns the value after restricting it to the periodic boundary
/// condition [0, 1).
/// See https://forums.swift.org/t/why-no-fraction-in-floatingpoint/10337
@_transparent
func wrappedToUnitRange() -> Self {
let fract = self - self.rounded(.down)
// Have to clamp to just below 1 because very small negative values
// will otherwise return an out of range result of 1.0.
// Turns out this:
if fract >= 1.0 { return Self(1).nextDown } else { return fract }
// is faster than this:
//return min(fract, Self(1).nextDown)
}
@_transparent
func wrapped(to range: Range<Self>) -> Self {
let measure = range.upperBound - range.lowerBound
let recipMeasure = Self(1) / measure
let scaled = (self - range.lowerBound) * recipMeasure
return scaled.wrappedToUnitRange() * measure + range.lowerBound
}
@_transparent
func wrappedIteratively(to range: Range<Self>) -> Self {
var v = self
let measure = range.upperBound - range.lowerBound
while v >= range.upperBound { v = v - measure }
while v < range.lowerBound { v = v + measure }
return v
}
}
我问了一些可能感兴趣的与simd相关的问题
编辑2:
从以上Swift论坛帖子中可以看出:
// Note that tiny negative values like:
let x: Float = -1e-08
// May produce results outside the [0, 1) range:
let wrapped = x - floor(x)
print(wrapped < 1.0) // false
// which may result in out-of-bounds table accesses
// in common usage, so it's probably better to use:
let correctlyWrapped = simd_fract(x)
print(correctlyWrapped < 1.0) // true
//请注意微小的负值,如:
设x:Float=-1e-08
//可能产生超出[0,1]范围的结果:
让包裹=x-地板(x)
打印(包装<1.0)//错误
//这可能会导致越界表访问
//通常情况下,最好使用:
让正确包装=简单分形(x)
打印(正确包装<1.0)//true
我已经更新了代码来解释这个问题。有趣的问题。如果Swift有
fmod()的实现
最好使用它,而不是试图直接旋转位。浮点表示有很多微妙之处。我不认为像尾数部分上的位掩码这样简单的东西就足够了。fmod
与您将要得到的一样好。它需要除法,这通常是一个缓慢的过程说明。结果将是精确的(不会引入新的算术错误)。如果您知道操作数在间隔外少于一个周期,则一个或两个比较和相加或比较和减去序列可能会更快。感谢您提供的大量信息!特别是关于modf(x,&dummy)比fmod(x,1)更快的部分.由于我不知道如何正确解释“97%的速度”(我猜是快了近两倍?),我自己做了一些测试,发现modf(x,&dummy)比fmod(x,1)快6倍左右(我不知道如何正确地转换为速度百分比)。@Jens:97%的速度意味着快了1.97倍(或者运行时间缩短了0.51倍,或者运行时间缩短了49%)。我惊讶于速度提高了6倍:也许你的modf
是内联的?另外,对于单位范围[0,1],我们可以使用(x-floor(x)),根据我的测试,它比modf(x,&dummy)快很多,比simd_fract(x)稍快一些。它的另一个好处是,它可以按原样处理负数,总是返回[0,1]以内的值。@Jens:我同意floor
更快。我觉得自己不考虑它很傻;我想我主要考虑的方法是可以处理范围为2的任意幂的方法(比如整数)。你想让我更新这个答案,还是我们只宣传你的答案?@David:请随时更新这个答案!出于某种原因,在我写下上述评论之前,我实际上也没有考虑过地板。我将用simd float2上的一些信息更新我的答案。
extension BinaryFloatingPoint {
/// Returns the value after restricting it to the periodic boundary
/// condition [0, 1).
/// See https://forums.swift.org/t/why-no-fraction-in-floatingpoint/10337
@_transparent
func wrappedToUnitRange() -> Self {
let fract = self - self.rounded(.down)
// Have to clamp to just below 1 because very small negative values
// will otherwise return an out of range result of 1.0.
// Turns out this:
if fract >= 1.0 { return Self(1).nextDown } else { return fract }
// is faster than this:
//return min(fract, Self(1).nextDown)
}
@_transparent
func wrapped(to range: Range<Self>) -> Self {
let measure = range.upperBound - range.lowerBound
let recipMeasure = Self(1) / measure
let scaled = (self - range.lowerBound) * recipMeasure
return scaled.wrappedToUnitRange() * measure + range.lowerBound
}
@_transparent
func wrappedIteratively(to range: Range<Self>) -> Self {
var v = self
let measure = range.upperBound - range.lowerBound
while v >= range.upperBound { v = v - measure }
while v < range.lowerBound { v = v + measure }
return v
}
}
extension float2 {
@_transparent
func wrappedInUnitRange() -> float2 {
return simd.fract(self)
}
@_transparent
func wrappedToMinusOneToOne() -> float2 {
let scaled = (self + float2(1, 1)) * float2(0.5, 0.5)
let scaledFract = scaled - floor(scaled)
let wrapped = simd_muladd(scaledFract, float2(2, 2), float2(-1, -1))
// Note that we have to make sure the result is not out of bounds, like
// simd fract does:
let oneNextDown = Float(bitPattern:
0b0_01111110_11111111111111111111111)
let oneNextDownFloat2 = float2(oneNextDown, oneNextDown)
return simd.min(wrapped, oneNextDownFloat2)
}
@_transparent
func wrapped(toLowerBound lowerBound: float2,
upperBound: float2) -> float2
{
let measure = upperBound - lowerBound
let recipMeasure = simd_precise_recip(measure)
let scaled = (self - lowerBound) * recipMeasure
let scaledFract = scaled - floor(scaled)
// Note that we have to make sure the result is not out of bounds, like
// simd fract does:
let wrapped = simd_muladd(scaledFract, measure, lowerBound)
let maxX = upperBound.x.nextDown // For some reason, this won't be
let maxY = upperBound.y.nextDown // optimized even when upperBound is
// statically known, and there is no similar simd function available.
let maxValue = float2(maxX, maxY)
return simd.min(wrapped, maxValue)
}
}
// Note that tiny negative values like:
let x: Float = -1e-08
// May produce results outside the [0, 1) range:
let wrapped = x - floor(x)
print(wrapped < 1.0) // false
// which may result in out-of-bounds table accesses
// in common usage, so it's probably better to use:
let correctlyWrapped = simd_fract(x)
print(correctlyWrapped < 1.0) // true