如何存储非常小的数字
本文关键字:非常 数字 存储 何存储 | 更新日期: 2023-09-27 18:23:56
我有一个公式来计算C#中一些数字的权重。我有9个重量。这些权重的总和是1,所以这些权重太小了。例如,在计算这些权重后,结果如下:
1/17,1/17,1/17,1/17
当我想将这些权重存储在double
类型的参数中时,存储的值将是:
0.0,0.0,0.0,0.0,0.0,0.0.,0.0,0.0
问题是,我需要这些权重的确切值才能在另一个公式中使用,而它们在那个公式中太重要了。它们的和应该是1。
我能为解决这个问题做些什么?
double
完全能够存储关于1/17
的。它将无法以完美的准确性和精确性做到这一点。但也许这对你来说已经足够了。您的错误可能是使用整数除法。尝试1.0/17.0
。
只有能够存储形式a/b
的一部分的数据类型才能准确地存储1/17
。任何有限精度的数字都不能(由于计算机内存是有限的,所以所有的数字都是有限精度的)。
让我们请SMT Solver Z3提供一个示例,其中1.0/divisor*divisor != 0.0
。浮动的一个例子是:
float divisor = (float)(int)(1.296875 * 64);
Console.WriteLine(divisor); //83
Console.WriteLine(1.0f/divisor*divisor == 1.0); //False
使用以下SMTlib输入和Z3在线找到:
(set-logic QF_FPA)
(declare-const x (_ FP 8 24))
(declare-const divisor (_ FP 8 24))
(declare-const r1 (_ FP 8 24))
(declare-const r2 (_ FP 8 24))
(assert (and
(not (or (= x (as NaN (_ FP 8 24))) (= x (as plusInfinity (_ FP 8 24))) (= x (as minusInfinity (_ FP 8 24)))))
(not (or (= divisor (as NaN (_ FP 8 24))) (= divisor (as plusInfinity (_ FP 8 24))) (= divisor (as minusInfinity (_ FP 8 24)))))
(not (or (= r1 (as NaN (_ FP 8 24))) (= r1 (as plusInfinity (_ FP 8 24))) (= r1 (as minusInfinity (_ FP 8 24)))))
(not (or (= r2 (as NaN (_ FP 8 24))) (= r2 (as plusInfinity (_ FP 8 24))) (= r2 (as minusInfinity (_ FP 8 24)))))
(> divisor ((_ asFloat 8 24) roundTowardZero 2.0 0))
(< divisor ((_ asFloat 8 24) roundTowardZero 100.0 0))
(== divisor (roundToIntegral roundTowardZero divisor))
(= x ((_ asFloat 8 24) roundTowardZero 1.0 0))
(= r1 (/ roundNearestTiesToEven x divisor))
(= r2 (* roundNearestTiesToEven r1 divisor))
(not (== r2 ((_ asFloat 8 24) roundTowardZero 1.0 0)))
))
(check-sat)
(get-model)
Z3具有比IEEE浮点运算更精确的推理能力。得到的模型是:
(model
(define-fun r2 () (_ FP 8 24)
(as +1.99999988079071044921875p-1 (_ FP 8 24)))
(define-fun divisor () (_ FP 8 24)
(as +1.296875p6 (_ FP 8 24)))
(define-fun x () (_ FP 8 24)
(as +1p0 (_ FP 8 24)))
(define-fun r1 () (_ FP 8 24)
(as +1.54216861724853515625p-7 (_ FP 8 24)))
)
如果不使用有理数,1/17的精确表示将很难实现。decimal
将为您提供比double
更高的精度表示。
首先,不应该使用1/17
来计算确切的值;编译器将其视为int 0,然后将int 0转换为双0.0。您可以这样编码:1.0/17.0
或1/(double)17
。
其次,如果希望值的总和为1,可以使用Math.Round(1.0/17*10000)
/10000
来获得点后有五个数字的精确小数。