Avatar billede thecokeguy Nybegynder
04. juli 2003 - 04:02 Der er 33 kommentarer og
1 løsning

Helt tal

Hvordan spørger man delphi om en float er et helt tal?

F.eks:
VAR
  i: Float;
BEGIN
IF i = "helt tal: 1,2,3,4,5..." THEN
  Close;
END;
Avatar billede tjp Mester
04. juli 2003 - 04:13 #1
Kender ikke delphi, men et hack kunne jo være at sammenligne 'i' med sin egen afrundning som formentlig også her hedder noget med Floor eller Ceil!
Avatar billede runem74 Nybegynder
04. juli 2003 - 07:39 #2
Jeg har lavet et lille eksemple.


procedure TForm1.Button1Click(Sender: TObject);
var
a,b:real;
begin
a:=1;
b:=2.34;
if round(b)=b then
    begin
    label2.caption:='b';
    end;
if round(a)=a then
    begin
    label1.caption:='a';
    end;
end;

Mange hilsner,

Rune
Avatar billede snowball Novice
04. juli 2003 - 08:17 #3
Det kan du meget nemt finde ud af ved at prøve og konvertere den. Se nedenstående eksempel:

procedure TForm1.Button1Click(Sender: TObject);
var
  tmpInt: Integer;
begin
  Try
    tmpInt := StrToInt(Edit1.Text);
    Showmessage('Det er et heltal');
  Except
    Showmessage('Det er IKKE et heltal');
  End;
end;

Hvis det der står i Edit1.Text ikke er et heltal, så bliver Showmessage('Det er et heltal'); aldrig udført og den hopper ned i Except delen af koden. (Kører du dette eksempel inde fra Delphi, vil du få en fejlmeddelelse når den hopper ned i Except delen, men den fejl bliver ikke vist hvis du bare kører .exe filen uden for Delphi!)

Snowball
Avatar billede arne_v Ekspert
04. juli 2003 - 08:44 #4
Floats er jo ikke eksakte så du bør teste på om den er "tæt nok"
på en integer - noget a la:

function isint(x : real) : boolean;

begin
  if(abs(round(x)-x) < 0.00001) then
      isint := true
  else
      isint := false;
end;

hvor:

program testisint;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function isint(x : real) : boolean;

begin
  if(abs(round(x)-x) < 0.00001) then
      isint := true
  else
      isint := false;
end;

begin
  writeln(isint(123));
  writeln(isint(123.456));
  writeln(isint(122.9999));
  writeln(isint(123.0001));
  writeln(isint(122.999999));
  writeln(isint(123.000001));
end.

udskriver:

TRUE
FALSE
FALSE
FALSE
TRUE
TRUE
Avatar billede runem74 Nybegynder
04. juli 2003 - 09:26 #5
arne V-> Dette er kun et problem hvis du sammenligner float og integer. Prøv mit eksemple det virker!

mvh,

Rune
Avatar billede arne_v Ekspert
04. juli 2003 - 10:37 #6
Rune>

1)  du sammenligner faktisk en float point og en integer

2)  det er også et problem at sammenligne 2 floating points

3)  jeg er ikke i tvivl om at 1 og 2.34 virker - men det er jo ikke
    ligefrem de mest problematiske test data
Avatar billede runem74 Nybegynder
04. juli 2003 - 11:26 #7
ok ;-)
Avatar billede zerohero Nybegynder
04. juli 2003 - 17:06 #8
Dette virker:

function HeltTal(Tal : Single) : Boolean;
begin
  if Frac(Tal) = 0 then
  Result := True
  else
    Result := False;
end;

Returnere True hvis det Tal er et helt tal ellers returneres et False;

Eks:

const
  I = 0.5; //bare ændre dit tal værdi her!!!
begin
    if HeltTal(I) then
  ShowMessage('Helt tal!')
  else
    ShowMessage('Ikke helt tal!');
end;

Held og Lykke
ZeroHero
Avatar billede arne_v Ekspert
04. juli 2003 - 19:27 #9
zerohero>

Dit eksempel er reelt det samme som rune's.

round og frac virker "ens" (i denne sammenhæng).

Og har samme problem.
Avatar billede arne_v Ekspert
04. juli 2003 - 19:27 #10
Og inden folk konkluderer at jeg er kuleskør, så prøv lige
følgende lille program:

program testisint;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function isint1(x : real) : boolean;

begin
  isint1 := (round(x)=x);
end;

function isint2(x : real) : boolean;

begin
  isint2 := (abs(round(x)-x) < 0.00001);
end;

function isint3(x : real) : boolean;

begin
  isint3 := (frac(x)=0);
end;

procedure test(x : real);

begin
  writeln(x:7:5,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5);
end;

var
  i : integer;
  x : real;

begin
  test(123);
  test(123.456);
  test(122.9999);
  test(123.0001);
  test(122.999999);
  test(123.000001);
  for i:=1 to 1000 do begin
      x := 1/i;
      x := i*x;
      if (isint1(x)<>isint2(x)) or (isint2(x)<>isint3(x)) then begin
        writeln(i:3,' ',x:9:7,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5);
      end;
  end;
end.
Avatar billede arne_v Ekspert
04. juli 2003 - 19:32 #11
Vi ser at isint1 (round) og isint3 (frac) returnerer false for nogle af de
tal der reelt er heltal.

Årsagen er at udregninger med floating point altid har en lille smule
usikerhed og så kan de test fejle.

isint2 derimod tester på et interval og har derfor ikke problemet.

(du kan naturligvis regulere 0.00001 til noget du finder passende)
Avatar billede zerohero Nybegynder
04. juli 2003 - 21:57 #12
arne_v: Hør her, hvis du vil se om et tal er helt eller ej, så behøves der altså ikke den store udvikling af den "dybe tallerken" til. Jeg arbejder normalt med 3D geometry og i den forbindelse bruger jeg altid Single - og jeg har AlDRIG haft problemer med dette. Jeg afviser bestemt ikke at din metode IKKE er korrekt, men hvorfor dælen bruge så meget hjerne-vridning for et så simpelt en procedure som denne, hvis det kun skal bruges til et eller andet simpelt!!!
Jeg holder stadig på at, hvis et tal skal være helt, må alt der er til højre for kommaet være lig nul og ABSOLUT NUL - det behøves der nu ikke meget professor-viden til for at finde ud af - selv med floats! Frac afrunder desuden ikke!

if Frac(Tal) <> 0 then
Result := False //Ikke Helt
else
  Result := True;

ZeroHero
(Måske har jeg misforstået noget) ;)

PS: I fleste sammenhænge (afhængigt af projektets kunnen - præcision) foretrækker jeg så simpelt kode som muligt!!! Overblik er mere værd end overkill!!! ;))
Avatar billede arne_v Ekspert
04. juli 2003 - 22:14 #13
Hvis du prøver at køre mit eksempel vil du se, at man kan få problemer - endda
at det er rimeligt nemt at få problemet.

Jeg er også til simple løsninger. Men til simple korrekte løsninger.
En simpel løsning der kun virker i 99% af tilfældende er ikke god
kode.

Og min løsning er faktisk ikke så kompleks. Mindre end 20 bogstaver
mere end din løsning. 20 bogstaver er ikke slemt.

Jeg har aldrig påstået at frac runder af, men round(x)=x og frac(x)=0
er reelt samme test.

Floating point tal på computer har nogle egenskaber som afviger
en del fra almindelige matematiske regler og derfor skal man være
meget forsigtig med at bruge almindelig matematisk logik på dem.

Du kan godt definere at en floating point value kun er 1 hvis
den har et ganske betsmt bit mønster.

Det er tanke-gangen bag både round og frac metoderne.

Men det har som mit program demonstrerer den kosekvens at
(1/49)*49 ikke er 1.

Det kan give problemer - store problemer.

Mit test bygger på at en floating point value er 1 hvis
den ligger i intervallet fra 1 - meget lidt til 1 + meget lidt.

Det virker umiddelbart sjusket, men lige pludselg er (1/49)*49 igen 1
og det passer altså bedre.
Avatar billede arne_v Ekspert
04. juli 2003 - 22:18 #14
Delphi indeholder sågar funktioner til at håndtere dette.

function isint4(x : real) : boolean;

begin
  isint4 := iszero((round(x)-x, 0.00001);
end;

er helt ekvivalent til isint2.
Avatar billede arne_v Ekspert
04. juli 2003 - 22:18 #15
program testisint;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function isint1(x : real) : boolean;

begin
  isint1 := (round(x)=x);
end;

function isint2(x : real) : boolean;

begin
  isint2 := (abs(round(x)-x) < 0.00001);
end;

function isint3(x : real) : boolean;

begin
  isint3 := (frac(x)=0);
end;

function isint4(x : real) : boolean;

begin
  isint4 := iszero((round(x)-x, 0.00001);
end;

procedure test(x : real);

begin
  writeln(x:7:5,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5,' ',isint4(x):5);
end;

var
  i : integer;
  x : real;

begin
  test(123);
  test(123.456);
  test(122.9999);
  test(123.0001);
  test(122.999999);
  test(123.000001);
  for i:=1 to 1000 do begin
      x := 1/i;
      x := i*x;
      if (isint1(x)<>isint2(x)) or (isint2(x)<>isint3(x)) then begin
        writeln(i:3,' ',x:9:7,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5,' ',isint4(x):5);
      end;
  end;
end.
Avatar billede zerohero Nybegynder
04. juli 2003 - 22:55 #16
Har lige testet dit eksempel, og jaaahhhh det virker som om at der er noget om snakken. Så snart der mere end 5 decimaler efter kommaet, så begynder det at gå knap så godt for mit eksempel ;(( Tjaaah der er jo ikke andet for end at jeg må bøje mig i støvet (snøft) - Men så blev jeg da lidt klogere - takker ;))
Avatar billede zerohero Nybegynder
04. juli 2003 - 22:56 #17
Du må lære mig lidt mere om Floats så! Hvorfor var det lige at de er så upræcise?
Avatar billede zerohero Nybegynder
04. juli 2003 - 23:03 #18
Nææh vent lige lidt!!! Jeg afprøvede dit kode sådan her og den returnere nøjatig som det samme som din isint2 - den som du siger god for!

function isint1(x : real) : boolean;
begin
  isint1 := (round(x)=x);
end;

function isint2(x : real) : boolean;
begin
  isint2 := (abs(round(x)-x) < 0.00001);
end;

function isint3(x : real) : boolean;
begin
  isint3 := (frac(x)=0);
end;

function HeltTal(Tal : Single) : Boolean;
begin
  if Frac(Tal) = 0 then
  Result := True
  else
    Result := False;
end;

procedure test(x : real);
var
  b1, b2, b3, b4 : Boolean;
  s1, s2, s3, s4 : string;
begin
  b1 := isint1(x);
  b2 := isint2(x);
  b3 := isint3(x);
  b4 := HeltTal(x);

  if b1 then
  s1 := 'True'
  else
    s1 := 'False';

  if b2 then
  s2 := 'True'
  else
    s2 := 'False';

  if b3 then
  s3 := 'True'
  else
    s3 := 'False';

  if b4 then
  s4 := 'True'
  else
    s4 := 'False';

  ShowMessage(s1+' '+s2+' '+s3+' '+s4);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  b1, b2, b3 : Boolean;
  s1, s2, s3 : string;
begin
  test(123);
  test(123.456);
  test(122.9999);
  test(123.0001);
  test(122.999999);
  test(123.000001);
end;
Avatar billede arne_v Ekspert
04. juli 2003 - 23:16 #19
Det var faktisk ikke den første del jeg klandrede frac metoden for - det
var den sidste del med (1/i)*i.

Skiftet fra real til single ændrer lidt på hvor fejlene kommer,men de kommer stadig.

Prøv og kør:

program testisint;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function isint1(x : single) : boolean;

begin
  isint1 := (round(x)=x);
end;

function isint2(x : single) : boolean;

begin
  isint2 := (abs(round(x)-x) < 0.00001);
end;

function isint3(x : single) : boolean;

begin
  isint3 := (frac(x)=0);
end;

procedure test(x : single);

begin
  writeln(x:7:5,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5);
end;

var
  i : integer;
  x : single;

begin
  test(123);
  test(123.456);
  test(122.9999);
  test(123.0001);
  test(122.999999);
  test(123.000001);
  for i:=1 to 1000 do begin
      x := 1/i;
      x := i*x;
      if (isint1(x)<>isint2(x)) or (isint2(x)<>isint3(x)) then begin
        writeln(i:3,' ',x:9:7,' ',isint1(x):5,' ',isint2(x):5,' ',isint3(x):5);
      end;
  end;
end.
Avatar billede arne_v Ekspert
04. juli 2003 - 23:27 #20
Floating point tal på computere er repræsenteret som:

(b1*1/2 + b2*1/4 + b3*1/8 + ... + bn*1/2^n) * 2^m

Hvor b1..bn er bits (0 eller 1) i den del man kalder manissen og
m er exponenten.

Og allerede nu begynder man jo at få en mistanke om at alle tal
ikke nødvendigvis kan repræsenteres i den form (meget lig problem
stillingen med at 1/3 ikke kan repræsenteres som et endeligt
antal decimaler  0.3333333333333 og i det uendelige).

En anden måde at måde at analyser det på er at der jo åbenlyst kun
2^32 mulige floating point tal for 32 bit floating point og 2^64
for 64 bit. Smamtidift ved vi jo at der er et uendeligt antal
reelle tal (i den matematiske betydning) mellem hvilket som helst 2
forskellige tal. Igen konkluderer vi at ikke alle tal kan repræsenteres
eksakt som floating point på en computer.

1 kan repræsentres eksakt som 1*1/2*2^1.

49 kan repræsenteres eksakt som (1*1/2+1*1/4+1*1/64)*2^6

Men 1/49 kan ikke repræsenteres eksakt der skr en afrundings fejl
og når vi ganger op med 49 igen ender vi ikke nødvendigvis på 1*1/2*2^1
men kun på noget som er tæt på.

Men hvis noget er tilpas tæt på så giver det mening at definere det som
værende det samme.

Det matcher også udmærker til mange problem-stillinger i den virkelige
verden.

Hvsi jeg spørger dig om du har et helt antal kilometer mellem hjem
og arbejde, så måler du ikke op i millimeter. Kilometer tælleren
i din bil tager kun 0.1 km og derfor bliver alt mellem 49.95 og 50.05
lig med 50.0.

Andre problem-stillinger gælder ikke det samme for. Penge f.eks..
Bogholdere og revisorer har en opfattelse af at regnskaber skal
stemme absolut - små afrundings fejl er ikke acceptable. Derfor
må man aldrig bruge floating point til beløb. Eller for igen at vende
tilbage til problem-stillingen. Et beløb er et helt antal kroner
eller ej - 50.00 kr. er ikke 49.995-50.005 kr..
Avatar billede tjp Mester
05. juli 2003 - 02:16 #21
>> Kommentar til arne_v 04/07-2003 23:27:25
Ifølge IEEE standarden, som nok er den mest benyttede, lægges 1 underforstået til mantissens brøk, så 1 burde reprænsenteres med lutter nuller, altså: 1,00000...0 * 2^0.

Dog benytter exponenten biased notation, hvorved den unsigned værdi fratrækkes 127 (ved 8 bit), så exponenten er derfor 01111111.

For at gøre tingene endnu mere mudret arbejder standarden med denormaliserede numre for meget små værdier, hvor det foranstillet 1-tal fjernes ved at sætte exponenten til 00000000.

Og for resten har vi helt glemt at nævne at der også er en sign-bit forrest. ;-)

(ps: jeg synes spørgeren glimrer lidt med sit fravær!*S*)
Avatar billede arne_v Ekspert
05. juli 2003 - 21:31 #22
Jep.

Men det har ikke nogen betydning for hvilke tal der kan repræsenteres
eksakt kun for hvordan bittene fysisk gemmes.
Avatar billede nop Nybegynder
07. juli 2003 - 11:49 #23
Jeg har osse erfaret at det kan være besværligt at arbejde med reel eller extended som jeg har brugt i forbindelse med nogle input felter som jeg validerer og konvereterer, helt på danskt, her vil 999,99 fx blive 999,99800021 (eller der omkring) når man looper chars igennem og ganger op, før og efter kommaet, så dethe.....
Avatar billede arne_v Ekspert
07. juli 2003 - 12:15 #24
For beløb og andre fixed point størrelser: brug integer
og husk hvor komma skal stå (for beløb: regn i ører).

For rigtige floating point tal:
  aldrig if x=123.456
  altid if abs(x-123.456) < megetlidt
Avatar billede nop Nybegynder
07. juli 2003 - 12:50 #25
Det har arne_v da ret i men der er (6.0 ihvertilfælde) nogle funktioner i unit'en "math" til netop dette formål: samevalue, comparevalue og iszero som opererer med en epsilon værdi ligesom "megetlidt" i arne_v's eksempler. Kig i hjælp for yderlige oplysninger.
Du har dog stadig brug for noget a la:
  if iszero(frac(i),0.00001) then //tallet er "helt"
OG det var så en ny måde arne_v (som jo nok er den bedste bemærk fx at "abs" ikke er nødvendig).
Avatar billede nop Nybegynder
07. juli 2003 - 12:50 #26
Måske bliver min valgt ?
Avatar billede arne_v Ekspert
07. juli 2003 - 12:52 #27
nop>

iszero er faktisk allerede nævnt !

se "04/07-2003 22:18:20"
Avatar billede nop Nybegynder
07. juli 2003 - 12:57 #28
Ja nu så jeg det men du bruger stadig "abs(x-y)<megetlidt" til sammenligning af x og y, og det synes jeg ikke er godt nok.
Avatar billede arne_v Ekspert
07. juli 2003 - 13:06 #29
Det er præcis det samme.
Avatar billede nop Nybegynder
07. juli 2003 - 13:11 #30
Ja resultatet er det, samme ikke metoden.
Avatar billede arne_v Ekspert
07. juli 2003 - 13:37 #31
Hvad tror du iszero gør ?
Avatar billede nop Nybegynder
07. juli 2003 - 13:55 #32
Den gør nok (jeg ved ikke om der evt er nogle x87 instruktioner som kompinerer en/flere af arbejdsgangene) det samme, man kan så (nok) være sikker på at mellemresultaterne holdes i x87-registerne.
Man kunne forestille sig at hvis man brugte iszero (og de andre) så ville man være sikker på at det gik hurtigst muligt.
Vi skal heller ikke diskutere her det er ikke vores tråd, vi må håbe at thecokeguy har fået noget ud af vores indlæg.
Jeg synes det var en meget pædagogisk måde arne_v anskuliggjorde problemmet omkring fragmenter på, det er en metode man med held kan bruge til at blive fortrolig med andre funktioner på, fx strenge (copy,leftstr,rightstr,pos,val mv), arrays og osse fil-funktioner/procedure, altså stil en masse beregninger og showmessage's op med den/de funktioner som man er usikker(e) på, start simpelt og udvid langsomt hen mod det der er ens problem, og prøv evt at komme ud til krogene og opstil så nogle generalier for dig selv som du kan huske.
Venlig hilsen NOP.
Avatar billede arne_v Ekspert
22. juli 2003 - 22:36 #33
thecokeguy>

Har du fået svar på dit spørgsmål ?
Avatar billede arne_v Ekspert
31. juli 2003 - 08:25 #34
??
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

Seneste spørgsmål Seneste aktivitet
23 min siden Ny printer, men hvilken Af mort1 i Printere
I går 19:41 USB på Linux Af Uvanga i Linux
12/0818:26 Hvilken type mus Af mort1 i PC
11/0813:21 ms Forms Af leahcim i Andet software
10/0821:30 Blokeret på Snapchat? Af LineP i Chat & Messaging