2014-10-01 35 views
2

在C++中是否有一個算法允許我在給定浮點值V(例如double或float)的情況下返回給定方向上的最接近的值(向上或向下) 正好小於或等於指定的小數位數D?如何將浮點值轉換爲可以精確表示特定小數位數的最近值?

例如,給定

T = double 
V = 670000.08267799998 
D = 6 

對於方向朝= + INF我想的結果是670000.082678,和用於方向朝= -INF我想的結果是670000.082677

這有點類似於std :: nexttoward(),但有一個限制,即'下一個'值需要用最多D個小數位精確表示。我已經考慮過一個天真的解決方案,包括分離出小數部分,並用10^D縮放它,截斷它,然後再用10^-D縮放它,並將它重新粘到整數部分上,但是我不知道相信這可以保證結果值在底層類型中完全可以表達。

我希望有一種方法可以正確地做到這一點,但到目前爲止,我一直無法找到一個。


編輯:我覺得我原來的解釋沒有正確傳達我的要求。在@ patricia-shanahan的建議下,我會試着描述我的更高層次的目標,然後在這個背景下稍微改變一下這個問題。

在最高級別,我需要這個例程的原因是由於一些業務邏輯,其中我必須採用雙值K和百分比P,將其分成兩個雙分量V1和V2,其中V1〜P百分比的K和V1 + V2〜= K.捕獲的是,V1在通過有線協議發送給第三方之前用於進一步計算,該協議接受字符串格式的浮點值,最大值爲D小數位數。由於發送給第三方的值(以字符串格式)需要與使用V1進行計算的結果(雙格式)進行協調,因此我需要使用某個函數F()來「調整」V1,以使其爲儘可能接近K的P百分比,而仍然可以使用至多D小數位的字符串格式精確表示。 V2沒有V1的限制,可以計算爲V2 = K-F(V1)(可以理解並且可以接受的是,這可能導致V2使得V1 + V2非常接近但不完全等於K) 。

在較低的水平,我期待編寫程序來「調整」 V1的東西有以下特徵:

double F(double V, unsigned int D, bool roundUpIfTrueElseDown); 

,其中輸出是通過取V和(如果必要的話計算,並且按bool param指定的方向)將其舍入到Dth小數位。

我的期望是,當V被序列化了如下

const auto maxD = std::numeric_limits<double>::digits10; 
assert(D <= maxD); // D will be less than maxD... e.g. typically 1-6, definitely <= 13 
std::cout << std::fixed 
      << std::setprecision(maxD) 
      << F(V, D, true); 

然後輸出包含超過嗞小數位只有零。

重要的是要注意,出於性能原因,我正在尋找F()的實現,它不涉及在雙精度和字符串格式之間來回轉換。雖然輸出最終可能會轉換爲字符串格式,但在很多情況下,邏輯會在必要之前提前進行,我希望避免這種情況下的開銷。

+3

在您的例子,這兩個結果通過0.000001,不同這不完全表示爲二進制浮點數,所以它們中的至少一個也不能完全表示。 – 2014-10-01 21:54:11

+0

雖然0.000001不能完全表示爲雙精度,但我不確定這與我提出的問題有什麼關係。在這種情況下,我只關心輸出(例如670000.082677或670000.082678)是完全可以表示的;他們之間的差異並不需要用於我的目的。 – 2014-10-01 22:02:30

+3

我的觀點是,至少有一個是*不可代表的。 – 2014-10-01 22:04:24

回答

2

這是一個程序的草圖,可以完成所要求的任務。它主要是爲了找出這是否真的是想要的。我用Java編寫它,因爲該語言對我想依賴的浮點算法有一定的保證。我只使用BigDecimal來精確顯示雙精度,以顯示答案在小數點後不超過D位數的情況下完全可表示。

具體來說,我依賴於雙重行爲根據IEEE 754 64位二進制算術。對於C++,這很可能,但不能保證這個標準。我還依賴於Math.pow對於簡單確切的情況,精確的除以2的冪的精確性,以及能夠使用BigDecimal獲得精確的輸出。

我還沒有處理邊緣情況。最大缺失的部分是處理大數量的大數字D.我假設包圍二進制分數正好表示爲雙打。如果他們有超過53個有效位,情況就不一樣了。它還需要代碼來處理無限和NaN。對二次冪的劃分正確性的假設對於次正規數是不正確的。如果你需要你的代碼來處理它們,你將不得不糾正。

它基於這樣一個概念,即一個數字可以精確表示爲小數點後不超過D位的小數,並且可以精確地表示爲二進制小數,必須可以表示爲分母2的小數點D的力量。如果分母中需要2的更高次冪,則小數點後的小數點後面將需要超過D位。如果它根本不能被表示爲具有兩個冪分母的分數,那麼它不能完全表示爲雙精度。

雖然我跑了一些其他的情況進行說明,主要輸出:

670000.082678 to 6 digits Up: 670000.09375 Down: 670000.078125

下面是程序:

import java.math.BigDecimal; 

public class Test { 
    public static void main(String args[]) { 
    testIt(2, 0.000001); 
    testIt(10, 0.000001); 
    testIt(6, 670000.08267799998); 
    } 

    private static void testIt(int d, double in) { 
    System.out.print(in + " to " + d + " digits"); 
    System.out.print(" Up: " + new BigDecimal(roundUpExact(d, in)).toString()); 
    System.out.println(" Down: " 
     + new BigDecimal(roundDownExact(d, in)).toString()); 
    } 

    public static double roundUpExact(int d, double in) { 
    double factor = Math.pow(2, d); 
    double roundee = factor * in; 
    roundee = Math.ceil(roundee); 
    return roundee/factor; 
    } 

    public static double roundDownExact(int d, double in) { 
    double factor = Math.pow(2, d); 
    double roundee = factor * in; 
    roundee = Math.floor(roundee); 
    return roundee/factor; 
    } 
} 
+0

謝謝帕特里夏!你介意告訴我哪些FP算術在Java中依賴於Java?我想在C++中嘗試一下;也許一些相同的保證可以通過配置各種FP控制標誌來實現。 – 2014-10-02 13:09:19

+1

@ChrisKline我已經在算術依賴和邊緣案例的一些細節中進行了編輯。 – 2014-10-02 14:32:36

+0

謝謝你的額外信息,這是非常有用的。我相信我可以捕捉邊緣情況並適當地處理它們。 – 2014-10-02 16:02:59

1

總重寫。

基於OP的新的需求和使用電力的-2 @Patricia沙納漢,簡單的C解決方案的建議:

double roundedV = ldexp(round(ldexp(V, D)),-D); // for nearest 
double roundedV = ldexp(ceil (ldexp(V, D)),-D); // at or just greater 
double roundedV = ldexp(floor(ldexp(V, D)),-D); // at or just less 

這裏添加超越@Patricia Shanahan精解的唯一的事情是C代碼以匹配OP的標籤。

1

一般來說,小數不能精確地表示爲二進制分數。有一些例外情況,例如0.5(½)和16.375(16&#x215c;),因爲所有二進制分數都可精確表示爲小數。 (這是因爲2是10的因子,但10不是2的因子,或者是2的任何冪)。但是如果一個數不是2的某個冪的倍數,那麼它的二進制表示將是一個無限長的循環序列,就像&#x2153;以十進制(.333 ....)。

標準C庫提供宏DBL_DIG(通常爲15);具有許多十進制精度的十進制數可以轉換爲double(例如,scanf),然後轉換回十進制表示(例如,使用printf)。要在相反的方向上走向而不會丟失信息 - 從double開始,將其轉換爲十進制,然後將其轉換回來 - 您需要17位十進制數字(DBL_DECIMAL_DIG)。 (我引用的值是基於IEEE-754 64位雙打的)。

提供接近問題的一種方法是將不超過DBL_DIG精度的十進制數字視爲浮點數的「準確但不是真正準確」的表示形式,如果浮點數是最接近十進制數值的浮點數。找到浮點數的一種方法是使用scanfstrtod將十進制數轉換爲浮點數,然後嘗試使用附近的浮點數(使用nextafter進行探索)以找出哪些浮點數轉換爲相同用DBL_DIG表示精度的數字。

如果您信任的標準庫實施不會太遙遠,你可以轉換你的double使用sprintf十進制數,增加在所需的數位小數字符串(這只是一個字符串操作),然後將其轉換回doublestrtod

+0

@chux:對,我會解決這個問題,雖然現在我不知道爲什麼我包括它在內。 – rici 2014-10-02 03:20:39

0

在C++中,整數必須用二進制表示,但浮點類型可以有十進制表示。

如果FLT_RADIX<limits.h>是10或10的某個倍數,那麼您的精確表示小數值的目標是可以實現的。

否則,一般來說,這是不可能實現的。

因此,作爲第一步,嘗試找到FLT_RADIX爲10的C++實現。

我不擔心算法或其效率,直到C++實現安裝並證明可以在您的系統上運行。但是作爲一個暗示,你的目標似乎與「四捨五入」的操作有些相似。我認爲,在獲得了我的十進制浮點C++實現後,我開始研究四捨五入的技巧,例如,使用Google搜索,也許維基百科,&hellip;

相關問題