Avatar billede erve Nybegynder
12. maj 2007 - 16:33 Der er 13 kommentarer og
1 løsning

Afrunding RoundTo og SimpleRoundTo

Nogen der kan forklare hvorfor afrunding ikke fungerer i Delphi7 RoundTo og SimpleRoundTo
Nedenstående eksempel afrunder hårdnakket 74,085 til 74.08 mens 74,085000000000001 afrunders korrekt til 74.09
Det har formodentlif noget med SetRoundMode eller Set8087CW at gøre, men ligegyldigt hvad jeg gør fejler den. Hvem kan levere den skudsikre afrundingsmetode?

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Edit2: TEdit;
    btnSimpleRoundTo: TButton;
    btnRoundTo: TButton;
    procedure btnSimpleRoundToClick(Sender: TObject);
    procedure btnRoundToClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses Math;

{$R *.dfm}

procedure TForm1.btnSimpleRoundToClick(Sender: TObject);
var
  hjdouble1,hjdouble2 : double;
begin
  hjdouble1 := StrToFloat(Edit1.text);
  hjdouble2 := SimpleRoundTo(hjdouble1, -2);
  Edit2.Text := FloatToStr(hjdouble2);
end;

procedure TForm1.btnRoundToClick(Sender: TObject);
var
  hjdouble1,hjdouble2 : double;
begin
  hjdouble1 := StrToFloat(Edit1.text);
  hjdouble2 := RoundTo(hjdouble1, -2);
  Edit2.Text := FloatToStr(hjdouble2);
end;

end.
Avatar billede arne_v Ekspert
12. maj 2007 - 16:38 #1
et oplagt gæt vil være at 74,085 ikke kan repræsenteres eksakt i en double data type
og derfor gemmes som 74,08499999999999999999 og så gør afrunding jo det den skal
Avatar billede arne_v Ekspert
12. maj 2007 - 16:40 #2
floating point har nogle lidt specielle karakteristika

hvis du vil have mere "kroner og øre" opførsel skal du vælge en anden data type
Avatar billede erve Nybegynder
12. maj 2007 - 16:55 #3
Ja det er jo det der er problemet, men lidt utroligt hvis ingen har kunnet præstere den endegyldige afrundingsmetode til en Double?
Og lidt mærkeligt at der eksiterer en librarymetoder der reelt ikke fungerer
Avatar billede arne_v Ekspert
12. maj 2007 - 17:15 #4
jeg har lige kigget lidt - er du sikker på at det ikke er bankers rounding som narrer dig ?
Avatar billede arne_v Ekspert
12. maj 2007 - 17:16 #5
prøv lige

SetRoundMode(rmTruncate)

og

RoundTo(hjdouble1 + 0.005, -2);
Avatar billede arne_v Ekspert
12. maj 2007 - 17:18 #6
men altså helt generelt: kan du ikke leve med lidt usikkerhed på de sidste decimaler, så
skal du ikke bruge floating point
Avatar billede erve Nybegynder
12. maj 2007 - 17:35 #7
Æw Et øjeblik toede jeg lige det førsate forslag virkede men nu rundes f.eks -74,084 af til -74,07, og det er jo helt i skoven. Det andet virker heller ikke - fe.ks 74,084 -> 74.09.
Det kan nok ikke lade sig gøre.
Avatar billede arne_v Ekspert
12. maj 2007 - 17:46 #8
lav dine beregninger med integer og sæt selv dit komma, så kan du regne eksakt
Avatar billede hrc Mester
13. maj 2007 - 15:11 #9
Hvis din mindste værdi er "ører" registrer du ørene, fremfor et decimaltal, eksempelvis kr. 12,25. Gem det som 1255 ører. Derved får du både hurtigere sammentællinger og ingen decimaler at tumle med - hvis altså jeg har forstået arne_v korrekt.
Avatar billede hrc Mester
13. maj 2007 - 15:12 #10
Jeg kan normalt godt regne, men kr 12,25 er altså 1225 ører...
Avatar billede yates Nybegynder
14. maj 2007 - 09:59 #11
Erve:
Du skrev "lidt mærkeligt at der eksiterer en librarymetoder der reelt ikke fungerer".

Jeg har været udsat for dette fænomen ofte, og reelt drejer det dig ikke om at en library-metode ikke fungerer; problemet er at ingen datatype -uanset om vi taler Delphi, C, Basic, Python etc- kan repræsentere alle decimal-tal helt præcist.

I det konkrete tilfælde er du faldet ned i hullet fordi som arne siger din float lige præcis ikke kan repræsenteres exakt med "nuller og ettaller" som jo er byggestenen i alle taltyper.

Rent grundlæggende er problemet således hverken datatypen eller afrundingsalgoritmen, men derimod at brugeren (du selv) propper tal ind som ikke kan repræsenteres præcist. Diverse finurlige algorithmer kan ikke løse det grundlæggende problem at visse tal ikke kan repræsenteres med nuller og ettaller.

Hvis det er vigtigt for dog med 3 decimaler, så prøv evt
X=(round(Val*1000.0))/1000.0



Y.
Avatar billede borrisholt Novice
20. maj 2007 - 15:44 #12
Hvis du skal afrunde et tal til n decimaler bruger jeg den her :

function JBRoundFloat(Value: Extended; const NumberOfDecimals: Byte = 2): Extended;
var
  Factor: Extended;
begin
  Factor := IntPower(10, NumberOfDecimals);

  if Frac(Value * Factor) >= 0.5 then
    Value := Value + 1 / (IntPower(10, NumberOfDecimals + 1));

  Result := StrToFloat(Format('%.*f', [CurrencyDecimals, Value]));
end;


Hvis du der i mod vil regne med kroner og ører, og derfor have dit beløb afrundet til nærmeste 25 øre så brug det her :

type
  TRoundStruct = record
    NewMount: Double;
    Diff: Double;
  end;

function AfrundJB(const Amount: Double): TRoundStruct;
const
  CoinUnit = 25; //25 øre
begin
  Result.NewMount := JBRoundFloat(Amount / CoinUnit) * CoinUnit;
  Result.Diff := JBRoundFloat(Result.NewMount - Amount);
end;

function Afrund(const Amount: Double): Double;
begin
  Result := AfrundJB(Amount).NewMount;
end;

function FindRestVedAfrundning(const tal: Currency): Currency;
begin
  Result := AfrundJB(Tal).Diff;
end;


Skal du konvetere en streng til et tal kan jeg anbefale den her :

function SVal(s: string): Double;
begin
  s := StringReplace(s, CURRENCYstring, '', [rfReplaceAll, rfIgnoreCase]);

  if DecimalSeparator = '.' then
    s := StringReplace(s, ',', '.', [rfReplaceAll, rfIgnoreCase])
  else
    s := StringReplace(s, '.', ',', [rfReplaceAll, rfIgnoreCase]);

  s := StringReplace(s, ' ', '', [rfReplaceAll, rfIgnoreCase]);
  Result := StrToFloatDef(s, 0);
end;

Skal du sikre dig at der kun kan skrives noget bestemt i et exitfelt så brug den her :

type
  TCharSet = set of Char;

procedure ValidateEdit(Edit: TEdit; ValidChars: TCharSet = ['0'..'9', ',']);
var
  s: string;
  i: Integer;
begin
  s := Edit.Text;

  for i := Length(s) downto 1 do
    if not (s[i] in ValidChars) then
      Delete(s, i, 1);

  Edit.Text := s;
  Edit.SelStart := Length(s);
end;

Og svar på din oprindelige spørgsmål : "Nogen der kan forklare hvorfor afrunding ikke fungerer i Delphi7 ... "

Så finder du svaret i delphis online hjælp :

"In Delphi, the Round function rounds a real-type value to an integer-type value.

X is a real-type expression. Round returns an Int64 value that is the value of X rounded to the nearest whole number. If X is exactly halfway between two whole numbers, the result is always the even number. This method of rounding is often called "Banker’s Rounding".

If the rounded value of X is not within the Int64 range, a runtime error is generated, which can be handled using the EInvalidOp exception.

Note:    The behavior of Round can be affected by the Set8087CW procedure or SetRoundMode function."

Jeg gentager lige det vigtigste :

If X is exactly halfway between two whole numbers, the result is always the even number. This method of rounding is often called "Banker’s Rounding".

Så du må kode dine egne afrundings metoder som jeg har gjort. Hvis du bare vil afrunde til nærmeste heltal, efter de regler du har lært i skolen, gør du det vet at ligge 0,5 til og truncere resultatet.

Jens B
Avatar billede erve Nybegynder
20. maj 2007 - 17:41 #13
Jeg accepterer, fordi der er kode med.
Du diskuterer godt nok ikke simpleRoundTo, som ikke anvender bankers rounding, men derimod "asymmetric arithmetic rounding." Denne skulle virke.
Nu har jeg selv omkodet noget javakoder, der anvender et princip om at lægge et lille´bitte tal til. Den virker ret godt, men sikkert ikke 100% heller.
Avatar billede borrisholt Novice
26. maj 2007 - 17:17 #14
"Jeg accepterer, fordi der er kode med." ... Skriver du. En passende øvelse vil således være at lukke spørgsmålet, og ikke ligge et svar selv
Jens B
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester