是否可以强制浮点数的指数或尾数匹配另一个浮点数 (Python)?
Is it possible to force exponent or significand of a float to match another float (Python)?
前几天我试图解决这个有趣的问题。是否可以强制一个 float
的有效数或指数与 Python 中的另一个 float
相同?
出现这个问题是因为我试图重新缩放一些数据,以便最小值和最大值与另一个数据集匹配。但是,我重新调整后的数据略有偏差(小数点后 6 位),这足以引起问题。
为了给出一个想法,我有 f1
和 f2
(type(f1) == type(f2) == numpy.ndarray
)。我要np.max(f1) == np.max(f2) and np.min(f1) == np.min(f2)
。为此,我这样做:
import numpy as np
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
结果(仅作为示例)将是:
np.max(f1) # 5.0230593
np.max(f2) # 5.0230602 but I need 5.0230593
我最初的想法是强制 float
的指数是正确的解决方案。我在上面找不到太多东西,所以我根据需要做了一个 解决方法 :
exp = 0
mm = np.max(f1)
# find where the decimal is
while int(10**exp*mm) == 0
exp += 1
# add 4 digits of precision
exp += 4
scale = 10**exp
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
现在np.max(f2) == np.max(f1)
但是,有没有更好的方法呢?我做错什么了吗?是否可以将 float
重塑为类似于另一个 float
(指数或其他方式)?
编辑:按照建议,我现在使用:
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
虽然我上面的解决方案(对我的应用程序)有效,但我很想知道是否有解决方案可以以某种方式强制 float
具有相同的指数 and/or 尾数,以便数字将变得相同。
这取决于你的意思"mantissa."
在内部,浮点数使用以 2 为底的科学计数法存储。因此,如果您指的是 以 2 为尾数,实际上非常简单:只需乘以或除以 2 的幂(不是 10 的幂),尾数将保持不变(前提是指数没有超出范围;如果超出范围,您将被限制为无穷大或零,或者可能进入 denormal numbers,具体取决于关于建筑细节)。重要的是要了解,当您重新缩放 2 的幂时,小数展开将不匹配。这是使用此方法保留的二进制扩展。
但是,如果您指的是以 10 为底的尾数,不,浮点数是不可能的,因为重新缩放的值可能无法准确表示。例如,1.1 不能精确地以 2 为底(位数有限)表示,就像 1/3 不能以 10 为底(位数有限)表示一样。因此,将 11 缩小 1/10 无法完美准确地完成:
>>> print("%1.29f" % (11 * 0.1))
1.10000000000000008881784197001
但是,您可以使用 decimal
s. Decimals work in base 10, and will behave as expected in terms of base 10 rescaling. They also provide a fairly large amount of specialized functionality to detect and handle various kinds of loss of precision. But decimals don't benefit from NumPy speedups 执行后者,因此如果您有大量数据要处理,它们对于您的用例而言可能不够高效。由于 NumPy 依赖于硬件对浮点的支持,而大多数(所有?)现代架构不提供对 base 10 的硬件支持,这不容易补救。
TL;DR
使用
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
并确保您使用双精度,通过查看绝对或相对差异来比较浮点数,避免舍入以调整(或比较)浮点数,并且不要设置浮点数的基础组件手动编号。
详情
正如您所发现的,这不是一个很容易重现的错误。但是,使用浮点数容易出错。例如,将 1 000 000 000 + 0 . 000 000 000 1
相加得到 1 000 000 000 . 000 000 000 1
,但即使对于双精度(它支持 15 significant figures), so the trailing decimal is dropped. Moreover, some "short" numbers can't be represented exactly, as noted in @Kevin's . See, e.g., here 左右),这也是太多有效数字。(搜索类似 "floating point truncation roundoff error"甚至更多。)
这是一个确实说明问题的示例:
import numpy as np
numpy.set_printoptions(precision=16)
dtype=np.float32
f1 = np.linspace(-1000, 0.001, 3, dtype=dtype)
f2 = np.linspace(0, 1, 3, dtype=dtype)
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
print (f1)
print (f2)
输出
[ -1.0000000000000000e+03 -4.9999951171875000e+02 1.0000000474974513e-03]
[ -1.0000000000000000e+03 -4.9999951171875000e+02 9.7656250000000000e-04]
按照@Mark Dickinson 的,我使用了 32 位浮点数。这个和你报的错误是一致的,10^-7左右的相对误差,第7位有效数字附近
In: (5.0230602 - 5.0230593) / 5.0230593
Out: 1.791736760621852e-07
去 dtype=np.float64
让事情变得更好,但它仍然不完美。然后上面的程序给出
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 9.9999999997635314e-04]
这并不完美,但大体上已经足够接近了。比较浮点数时,您几乎不想使用严格相等,因为可能会出现上面提到的小错误。取而代之的是从另一个数中减去一个数并检查绝对差是否小于某个公差,and/or 查看相对误差。参见,例如,numpy.isclose
.
回到你的问题,看来应该可以做得更好。毕竟,f2
的范围是 0 到 1,因此您应该能够复制 f1
中的最大值。问题出在
行
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
因为当 f2
的一个元素是 1 时,你对它做的不仅仅是将 1 乘以 f1
的最大值,这会导致出现浮点运算错误的可能性.请注意,您可以将括号 f2*(np.max(f1)-np.min(f1))
乘以 f2*np.max(f1) - f2*np.min(f1)
,然后将结果 - f2*np.min(f1) + np.min(f1)
分解为 np.min(f1)*(f2-1)
,从而得到
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
所以当f2
的一个元素为1时,我们有1*np.max(f1) - np.min(f1)*0
。相反,当 f2
的元素为 0 时,我们有 0*np.max(f1) - np.min(f1)*1
。数字 1 和 0 可以 准确表示,因此应该没有错误。
修改后的程序输出
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
即随心所欲。
尽管如此,我仍然强烈建议只使用不精确的浮点数比较(如果需要,可以使用严格的界限),除非你有充分的理由不这样做。浮点运算中可能会出现各种微妙的错误,避免这些错误的最简单方法就是永远不要使用精确比较。
上面给出的另一种方法可能更可取,它是将 both 数组重新缩放到 0 和 1 之间。这可能是最适合在其中使用的形式该程序。 (如果需要,两个数组都可以乘以一个比例因子,例如 f1
的原始范围。)
重新使用舍入来解决你的问题,我不推荐这个。四舍五入的问题——除了它不必要地降低数据的准确性之外——是非常接近的数字可以在不同的方向上四舍五入。例如
f1 = np.array([1.000049])
f2 = np.array([1.000051])
print (f1)
print (f2)
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
print (f1)
print (f2)
输出
[ 1.000049]
[ 1.000051]
[ 1.]
[ 1.0001]
这与以下事实有关:虽然讨论数字与如此多的有效数字匹配很常见,但人们实际上并没有在计算机中以这种方式比较它们。您计算差异,然后除以正确的数字(相对误差)。
Re 尾数和指数,参见 math.frexp
和 math.ldexp
,记录在 here。但是,我不建议您自己设置这些(例如,考虑两个非常接近但指数不同的数字——您真的要设置尾数吗)。如果要确保数字完全相同(最小值也类似),直接将 f2
的最大值直接设置为 f1
的最大值会好得多。
这是带小数的
from decimal import Decimal, ROUND_05UP
num1 = Decimal('{:.5f}'.format(5.0230593)) ## Decimal('5.02306')
num2 = Decimal('{}'.format(5.0230602)) ## Decimal('5.0230602')
print num2.quantize(num1, rounding=ROUND_05UP) ## 5.02306
编辑** 我有点困惑为什么我会收到这么多负面反馈,所以这是另一个不使用小数的解决方案:
a = 5.0230593
b = 5.0230602
if abs(a - b) < 1e-6:
b = a
尝试将第二行替换为
f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1)
解释:差异可能出现在 2 个地方:
步骤 1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))
当您检查 np.min(f2)
和 np.max(f2)
时,您得到的是准确的 0 和 1 还是类似 1.0000003 的结果?
步骤 2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)
由于舍入误差,像 (a-b)+b
这样的表达式并不总是准确地产生 a
。建议的表达式稍微稳定一些。
非常详细的解释,请看
What Every Computer Scientist Should Know About Floating-Point Arithmetic 作者:大卫·戈德堡。
def rescale(val, in_min, in_max, out_min, out_max):
return out_min + (val - in_min) * ((out_max - out_min) / (in_max - in_min))
value_to_rescale = 5
current_scale_min = 0
current_scale_max = 10
target_scale_min = 100
target_scale_max = 200
new_value = rescale(value_to_rescale, current_scale_min, current_scale_max, target_scale_min, target_scale_max)
print(new_value)
new_value = rescale(10, 0, 10, 0, 100)
print(new_value)
答案:
150
100
前几天我试图解决这个有趣的问题。是否可以强制一个 float
的有效数或指数与 Python 中的另一个 float
相同?
出现这个问题是因为我试图重新缩放一些数据,以便最小值和最大值与另一个数据集匹配。但是,我重新调整后的数据略有偏差(小数点后 6 位),这足以引起问题。
为了给出一个想法,我有 f1
和 f2
(type(f1) == type(f2) == numpy.ndarray
)。我要np.max(f1) == np.max(f2) and np.min(f1) == np.min(f2)
。为此,我这样做:
import numpy as np
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
结果(仅作为示例)将是:
np.max(f1) # 5.0230593
np.max(f2) # 5.0230602 but I need 5.0230593
我最初的想法是强制 float
的指数是正确的解决方案。我在上面找不到太多东西,所以我根据需要做了一个 解决方法 :
exp = 0
mm = np.max(f1)
# find where the decimal is
while int(10**exp*mm) == 0
exp += 1
# add 4 digits of precision
exp += 4
scale = 10**exp
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
现在np.max(f2) == np.max(f1)
但是,有没有更好的方法呢?我做错什么了吗?是否可以将 float
重塑为类似于另一个 float
(指数或其他方式)?
编辑:按照建议,我现在使用:
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
虽然我上面的解决方案(对我的应用程序)有效,但我很想知道是否有解决方案可以以某种方式强制 float
具有相同的指数 and/or 尾数,以便数字将变得相同。
这取决于你的意思"mantissa."
在内部,浮点数使用以 2 为底的科学计数法存储。因此,如果您指的是 以 2 为尾数,实际上非常简单:只需乘以或除以 2 的幂(不是 10 的幂),尾数将保持不变(前提是指数没有超出范围;如果超出范围,您将被限制为无穷大或零,或者可能进入 denormal numbers,具体取决于关于建筑细节)。重要的是要了解,当您重新缩放 2 的幂时,小数展开将不匹配。这是使用此方法保留的二进制扩展。
但是,如果您指的是以 10 为底的尾数,不,浮点数是不可能的,因为重新缩放的值可能无法准确表示。例如,1.1 不能精确地以 2 为底(位数有限)表示,就像 1/3 不能以 10 为底(位数有限)表示一样。因此,将 11 缩小 1/10 无法完美准确地完成:
>>> print("%1.29f" % (11 * 0.1))
1.10000000000000008881784197001
但是,您可以使用 decimal
s. Decimals work in base 10, and will behave as expected in terms of base 10 rescaling. They also provide a fairly large amount of specialized functionality to detect and handle various kinds of loss of precision. But decimals don't benefit from NumPy speedups 执行后者,因此如果您有大量数据要处理,它们对于您的用例而言可能不够高效。由于 NumPy 依赖于硬件对浮点的支持,而大多数(所有?)现代架构不提供对 base 10 的硬件支持,这不容易补救。
TL;DR
使用
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
并确保您使用双精度,通过查看绝对或相对差异来比较浮点数,避免舍入以调整(或比较)浮点数,并且不要设置浮点数的基础组件手动编号。
详情
正如您所发现的,这不是一个很容易重现的错误。但是,使用浮点数容易出错。例如,将 1 000 000 000 + 0 . 000 000 000 1
相加得到 1 000 000 000 . 000 000 000 1
,但即使对于双精度(它支持 15 significant figures), so the trailing decimal is dropped. Moreover, some "short" numbers can't be represented exactly, as noted in @Kevin's
这是一个确实说明问题的示例:
import numpy as np
numpy.set_printoptions(precision=16)
dtype=np.float32
f1 = np.linspace(-1000, 0.001, 3, dtype=dtype)
f2 = np.linspace(0, 1, 3, dtype=dtype)
f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2)) # f2 is now between 0.0 and 1.0
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
print (f1)
print (f2)
输出
[ -1.0000000000000000e+03 -4.9999951171875000e+02 1.0000000474974513e-03]
[ -1.0000000000000000e+03 -4.9999951171875000e+02 9.7656250000000000e-04]
按照@Mark Dickinson 的
In: (5.0230602 - 5.0230593) / 5.0230593
Out: 1.791736760621852e-07
去 dtype=np.float64
让事情变得更好,但它仍然不完美。然后上面的程序给出
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 9.9999999997635314e-04]
这并不完美,但大体上已经足够接近了。比较浮点数时,您几乎不想使用严格相等,因为可能会出现上面提到的小错误。取而代之的是从另一个数中减去一个数并检查绝对差是否小于某个公差,and/or 查看相对误差。参见,例如,numpy.isclose
.
回到你的问题,看来应该可以做得更好。毕竟,f2
的范围是 0 到 1,因此您应该能够复制 f1
中的最大值。问题出在
f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1) # f2 is now between min(f1) and max(f1)
因为当 f2
的一个元素是 1 时,你对它做的不仅仅是将 1 乘以 f1
的最大值,这会导致出现浮点运算错误的可能性.请注意,您可以将括号 f2*(np.max(f1)-np.min(f1))
乘以 f2*np.max(f1) - f2*np.min(f1)
,然后将结果 - f2*np.min(f1) + np.min(f1)
分解为 np.min(f1)*(f2-1)
,从而得到
f2 = f2*np.max(f1)-np.min(f1)*(f2-1) # f2 is now between min(f1) and max(f1)
所以当f2
的一个元素为1时,我们有1*np.max(f1) - np.min(f1)*0
。相反,当 f2
的元素为 0 时,我们有 0*np.max(f1) - np.min(f1)*1
。数字 1 和 0 可以 准确表示,因此应该没有错误。
修改后的程序输出
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
[ -1.0000000000000000e+03 -4.9999950000000001e+02 1.0000000000000000e-03]
即随心所欲。
尽管如此,我仍然强烈建议只使用不精确的浮点数比较(如果需要,可以使用严格的界限),除非你有充分的理由不这样做。浮点运算中可能会出现各种微妙的错误,避免这些错误的最简单方法就是永远不要使用精确比较。
上面给出的另一种方法可能更可取,它是将 both 数组重新缩放到 0 和 1 之间。这可能是最适合在其中使用的形式该程序。 (如果需要,两个数组都可以乘以一个比例因子,例如 f1
的原始范围。)
重新使用舍入来解决你的问题,我不推荐这个。四舍五入的问题——除了它不必要地降低数据的准确性之外——是非常接近的数字可以在不同的方向上四舍五入。例如
f1 = np.array([1.000049])
f2 = np.array([1.000051])
print (f1)
print (f2)
scale = 10**(-np.floor(np.log10(np.max(f1))) + 4)
f2 = np.round(f2*scale)/scale
f1 = np.round(f1*scale)/scale
print (f1)
print (f2)
输出
[ 1.000049]
[ 1.000051]
[ 1.]
[ 1.0001]
这与以下事实有关:虽然讨论数字与如此多的有效数字匹配很常见,但人们实际上并没有在计算机中以这种方式比较它们。您计算差异,然后除以正确的数字(相对误差)。
Re 尾数和指数,参见 math.frexp
和 math.ldexp
,记录在 here。但是,我不建议您自己设置这些(例如,考虑两个非常接近但指数不同的数字——您真的要设置尾数吗)。如果要确保数字完全相同(最小值也类似),直接将 f2
的最大值直接设置为 f1
的最大值会好得多。
这是带小数的
from decimal import Decimal, ROUND_05UP
num1 = Decimal('{:.5f}'.format(5.0230593)) ## Decimal('5.02306')
num2 = Decimal('{}'.format(5.0230602)) ## Decimal('5.0230602')
print num2.quantize(num1, rounding=ROUND_05UP) ## 5.02306
编辑** 我有点困惑为什么我会收到这么多负面反馈,所以这是另一个不使用小数的解决方案:
a = 5.0230593
b = 5.0230602
if abs(a - b) < 1e-6:
b = a
尝试将第二行替换为
f2 = f2*np.max(f1) + (1.0-f2)*np.min(f1)
解释:差异可能出现在 2 个地方:
步骤 1) f2 = (f2-np.min(f2))/(np.max(f2)-np.min(f2))
当您检查 np.min(f2)
和 np.max(f2)
时,您得到的是准确的 0 和 1 还是类似 1.0000003 的结果?
步骤 2) f2 = f2*(np.max(f1)-np.min(f1)) + np.min(f1)
由于舍入误差,像 (a-b)+b
这样的表达式并不总是准确地产生 a
。建议的表达式稍微稳定一些。
非常详细的解释,请看 What Every Computer Scientist Should Know About Floating-Point Arithmetic 作者:大卫·戈德堡。
def rescale(val, in_min, in_max, out_min, out_max):
return out_min + (val - in_min) * ((out_max - out_min) / (in_max - in_min))
value_to_rescale = 5
current_scale_min = 0
current_scale_max = 10
target_scale_min = 100
target_scale_max = 200
new_value = rescale(value_to_rescale, current_scale_min, current_scale_max, target_scale_min, target_scale_max)
print(new_value)
new_value = rescale(10, 0, 10, 0, 100)
print(new_value)
答案:
150 100