舍入浮游物,使它们总和恰好为1 [英] Rounding floats so that they sum to precisely 1

查看:199
本文介绍了舍入浮游物,使它们总和恰好为1的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有相当多的代码,必须或多或少地随机生成一堆百分比,以十进制浮点形式存储。也就是说,它决定的材料之一,占总数的13.307%,然后存储在一个字典0.13307。



麻烦是,我永远不能得到这些数字加起来只有一个。老实说,我不完全确定问题是什么。这可能是与浮动的性质有关。



下面是有问题的代码,它的所有过于复杂的荣耀:

  while not sum(atmosphere.values())> = 1:
#随机选择一个素材
themat = random.choice(list如果在我们预测的温度下随机选择的材料是气态的,那么...
如果themat.vapor< temp:
#选择一个随机的百分比,它将组成我们星球的气氛,然后放在大气字典中。
atmosphere [themat] = round(random.uniform(0.001,0.5),5)

#查找分数加起来是否大于1
difference =(sum (atmosphere.values()) - 1)
#如果一个...
而差异> 0:
#选择一个随机成分
themat = random.choice(list(atmosphere.keys()))
#如果该成分的分数值高于我们需要的值总和减少到1 ...
如果氛围[themat]>(sum(atmosphere.values()) - 1):
#从中减去很多。
的气氛[themat] - =差
#然后打破循环,因为我们完成了,否则我们会继续删除一些气氛。
break
else:
#另外,将其百分比减半,减少差额减去气氛
oldperc = atmosphere [themat]
atmosphere [themat] = oldperc / 2
difference- = oldperc / 2

#最后,我们纠正前一个块的过度修正。
difference =(sum(atmosphere.values()) - 1)
if difference < 0:
#选择一个随机垫子
themat = random.choice(list(atmosphere.keys()))
#然后添加足够的总和为1
大气[themat] + =差异

对不起,如果我遗漏了一些明显的东西,或者不提供重要的一点信息,但我现在已经很累了,而且我一直在想这个好几天了。

解决方案

从你的代码看起来你是随机产生行星的气氛,大概是某种游戏什么的。无论如何,它的随机性令人信服我并不需要太准确。所以我建议你不要使用浮动,只是使用 int s并上升到100.然后你会得到你的确切的总结。对于任何数学,你只要使用它们就可以了。



这不是一个选项吗?

如果你坚持使用浮动,然后阅读...



你使用浮动的问题如下:

一个浮点数(在这种情况下是一个double)表示如下:

double 值的

>



所以,

你的数字是(1 + M)* 2 **(E)(其中 E = e-offset

1 + M 总是在1-2范围内。

一对二的幂(正数和负数),数字之间的间隔加倍指数的增加, E



想一想,这意味着在每个这些数字之间有可表示的数字之间的恒定间隔 [(1,2),(2,4),(4, 8)等] 。这也适用于两个负面的权力,所以:

  0.5  -  1 
0.25 - 0.5
0.125 - 0.25
0.0625 - 0.125

有相同数量的数字。这意味着如果你在(0.25,0.5)范围内取一个数字并将其添加到(0.5,1),那么你有50%的机会不能准确地表示数字。



如果将两个指数不同的浮点数相加 D ,那么总和可以精确表示的概率是2 -D

如果你想代表范围 0-1 ,那么你必须非常小心你使用的浮点数(即强制最后一个 N 小数部分为零,其中 N E )。

如果你沿着这条路线走下去,那么你将会得到更多的漂浮在范围的顶部而不是底部。



另一种方法是确定您希望得到多少接近零的值。可以说你想下降到0.0001。
$ b $ 0.0001 =(1 + M)* 2 E

所以我们将使用-14作为我们的最小指数。

log 2 (0.0001)= -13.28771 ... p $ p
$ b

p>

然后为了达到1,我们把指数作为-1。所以现在我们有13范围,每一个的值都是下一个值的两倍,而不用担心精确度。

这也意味着顶部范围有213 我们可以使用更多的值。这显然是不好的。



所以,在选择一个浮动后,然后转到最接近的允许值 - 在这种情况下, 我只是将最后的13位设置为零,并将其全部放入一个函数中,并在将它们从 rand

类似于:

  from ctypes import * 

def roundf(x,bitsToRound):

i = cast(pointer(c_float(x)),POINTER(c_int32))。contents.value

bits = bin(i)

bits = bits [: - bitsToRound] +0* bitsToRound

i = int(bits,2)

y = cast(pointer(c_int32(i)),POINTER(c_float))。contents.value

return y

(来自维基百科的图片)


I have a rather gnarly bit of code that must more-or-less randomly generate a bunch of percentages, stored as decimal floats. That is, it decides that material one makes up 13.307 percent of the total, then stores that in a dict as 0.13307.

The trouble is, I can never get those numbers to add up to exactly one. I'm not entirely certain what the problem is, honestly. It might be something to do with the nature of floats.

Here's the offending code, in all its overcomplicated glory:

while not sum(atmosphere.values())>=1:
    #Choose a material randomly
    themat=random.choice(list(materials.values()))

    #If the randomly chosen material is gaseous at our predicted temperature...
    if themat.vapor < temp:
        #Choose a random percentage that it will make up of our planet's atmosphere, then put it in the atmos dict.
        atmosphere[themat]=round(random.uniform(0.001,0.5),5)

#Find out if the fractions add up to more than 1
difference=(sum(atmosphere.values())-1)
#If one does...
while difference > 0:
    #Choose a random constituent
    themat=random.choice(list(atmosphere.keys()))
    #If that constituent has a higher fraction value than the amount we'd need to reduce the total to 1...
    if atmosphere[themat]>(sum(atmosphere.values())-1):
        #Subtract that much from it.
        atmosphere[themat]-=difference
        #Then break the loop, since we're done and otherwise we'd go on removing bits of the atmosphere forever.
        break
    else:
        #Otherwise, halve its percentage and reduce difference by the amount we reduced the atmosphere 
        oldperc=atmosphere[themat]
        atmosphere[themat]=oldperc/2
        difference-=oldperc/2

#Then, finally, we correct any overcorrections the previous block made.
difference=(sum(atmosphere.values())-1)
if difference < 0:
    #Choose a random mat
    themat=random.choice(list(atmosphere.keys()))
    #Then add enough to it that the total is 1.
    atmosphere[themat]+=difference

Sorry if I've missed something obvious, or am not providing an important bit of information, but I'm tired at the moment, and I've been trying to figure this out for days now.

解决方案

From your code it looks like you're randomly generating planet atmospheres, presumably for some kind of game or something. At any rate, the randomness of it is convincing me it doesn't need to be too accurate.

So i'd suggest you don't use floats, just use ints and go up to 100. Then you'll get your exact summing. For any maths you want to use them in just cast.

Is this not an option?

If you insist on using floats, then read on...

The problem you have using floats is as follows:

A floating point (in this case a double) is represented like this:

which corresponds to a double of value:

So,

your number is (1+M) * 2**(E) (where E = e-offset)

1+M is always in the range 1-2.

So, we have equally spaced numbers inbetween each pair of power of two (positive and negative), and the spacing between the numbers doubles with each increase in the exponent, E.

Think about this, it means that there is a constant spacing of representable numbers between each of these numbers [(1,2),(2,4),(4,8), etc]. This also applies to the negative powers of two, so:

0.5 - 1
0.25 - 0.5
0.125 - 0.25
0.0625 - 0.125
etc.

And in each range, there are the same quantity of numbers. This means that if you take a number in the range (0.25,0.5) and add it to a number in the range (0.5,1), then you have a 50% chance that the number cannot be exactly represented.

If you sum two floating point numbers for which the exponents differ by D, then the chances of the sum being exactly representable are 2-D.

If you then want to represent the range 0-1, then you'll have to be very careful about which floats you use (i.e. force the last N bits of the fraction to be zero, where N is a function of E).

If you go down this route, then you'll end up with far more floats at the top of the range than the bottom.

The alternative is to decide how close to zero you want to be able to get. Lets say you want to get down to 0.0001.

0.0001 = (1+M) * 2E

log2(0.0001) = -13.28771...

So we'll use -14 as our minimum exponent.

And then to get up to 1, we just leave the exponent as -1.

So now we have 13 ranges, each with twice as many values as the lower one which we can sum without having to worry about precision.

This also means though, that the top range has 213 more values we can use. This obviously isn't okay.

So, after picking a float, then round it to the nearest allowable value - in this case, by round I just mean set the last 13 bits to zero, and just put it all in a function, and apply it to your numbers immediately after you get them out of rand.

Something like this:

from ctypes import *

def roundf(x,bitsToRound):

    i = cast(pointer(c_float(x)), POINTER(c_int32)).contents.value

    bits = bin(i)

    bits = bits[:-bitsToRound] + "0"*bitsToRound

    i = int(bits,2)

    y = cast(pointer(c_int32(i)), POINTER(c_float)).contents.value

    return y

(images from wikipedia)

这篇关于舍入浮游物,使它们总和恰好为1的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆