FLOAT creation incorrect?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

FLOAT creation incorrect?

Eric Winger-4
Hi all!

Why does evaluating the following:

FLOAT new value: 1.2

result in:

a FLOAT(1.20000004768372)


Is this a bug or a result of my misuse of FLOAT?

If its a misuse, how would one setup a float in external memory?

btw - running D4 on NT4.

thx

Eric


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Jan Theodore Galkowski-2
Eric Winger wrote:

>
> Hi all!
>
> Why does evaluating the following:
>
> FLOAT new value: 1.2
>
> result in:
>
> a FLOAT(1.20000004768372)
>
> Is this a bug or a result of my misuse of FLOAT?
>

[snip]

Eric,

I don't really understand what you're trying to do here.  Is the
problem that you are interfacing with external code and you are
surprised to find a flonum you set to 1.2 on the outside become
something else within Dolphin?  Or are you surprised that when
you set a Dolphin number to a putative flonum you can manipulate
it and expose such far-to-the-right rubbish?  How did you
get that value anyway?  I mean, there's no "value:" message
appropriate in that context.  "value:" is used to feed parameters
to blocks, a.k.a., supply lambda expressions with arguments.

If r denotes your number, why not just write

   r := 1.2.

and be done with it?

Anyway, I did:

  r := 1.2.
  f := StdioFileStream write: 'foo.txt'.
  r printOn: f decimalPlaces: 30.
  f close.

and got

  1.200000000000000000000000000000

in the file "foo.txt".

On the other hand, it may be that you're struggling with
a "loss of innocence" discovering there are "more things in
heav'n and earth...than are dreamt on in the" integers.  In
that case you are in need of spiritual guidance.  Check out:

http://camden-www.rutgers.edu/HELP/Documentation/Sun-compiler-docs/WS6/manuals/common/ncg/ncg_goldberg.html

Hope you don't mind the gentle teasing.... (;-)}
Feel free to write back.

--
---------------------------------------------------------------------
 Jan Theodore Galkowski                    [hidden email]
 The Smalltalk Idiom                       [hidden email]
*********************************************************************
             "Smalltalk?  Yes, it's really that slick."
---------------------------------------------------------------------
Want to know more?  Check out
           http://www.dnsmith.com/SmallFAQ/
           http://www.object-arts.com/DolphinWhitePaper.htm
           http://st-www.cs.uiuc.edu/users/johnson/smalltalk/
*********************************************************************


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Eric Winger-5
Jan Theodore Galkowski wrote:

>
> Eric Winger wrote:
>
>>Hi all!
>>
>>Why does evaluating the following:
>>
>>FLOAT new value: 1.2
>>
>>result in:
>>
>>a FLOAT(1.20000004768372)
>>
>>Is this a bug or a result of my misuse of FLOAT?
>>
>>
>
> [snip]
>
> Eric,
>
> I don't really understand what you're trying to do here.  Is the
> problem that you are interfacing with external code and you are
> surprised to find a flonum you set to 1.2 on the outside become
> something else within Dolphin?


No, I'm testing some COM code and when I create an external structure
float object (i.e. instance of class FLOAT), I discovered a bug in my
code in that what was being created and sent out through an interface
(1.2) was coming back as 1.20000004768372). Thus my test would break.
So, after breaking down the code a smidgen, I narrowed it down to
something reproducable that could be posted.

The way I created my external float seemed odd. But I did it that way
because I couldn't find any instance creation methods in a FLOAT object
and no conversion method in Float. So I really wasn't sure if I should
have even expected the statement FLOAT new value: 1.2. (how the float
was created in Dolphin) to be a proper way to create a float that was
suitable for passing in COM. Something that the implementation object
would receive as 1.2. Or, if I had stumbled onto a bug.




>  Or are you surprised that when
> you set a Dolphin number to a putative flonum you can manipulate
> it and expose such far-to-the-right rubbish?  How did you
> get that value anyway?  I mean, there's no "value:" message
> appropriate in that context.  "value:" is used to feed parameters
> to blocks, a.k.a., supply lambda expressions with arguments.


Value: is the protocol that external structures get their underlying
reference or "value" values set in Dolphin. IOW, its how the underlying
address or bytes for an ST object in external memory. See any subclass
of ExternalStructure for a more specific example.


>
> If r denotes your number, why not just write
>
>    r := 1.2.
>
> and be done with it?


see above


>
> Anyway, I did:
>
>   r := 1.2.
>   f := StdioFileStream write: 'foo.txt'.
>   r printOn: f decimalPlaces: 30.
>   f close.
>
> and got
>
>   1.200000000000000000000000000000
>
> in the file "foo.txt".
>
> On the other hand, it may be that you're struggling with
> a "loss of innocence" discovering there are "more things in
> heav'n and earth...than are dreamt on in the" integers.  In
> that case you are in need of spiritual guidance.

>  Check out:
>
> http://camden-www.rutgers.edu/HELP/Documentation/Sun-compiler-docs/WS6/manuals/common/ncg/ncg_goldberg.html
>
> Hope you don't mind the gentle teasing.... (;-)}
> Feel free to write back.
>
>

Don't mind...When you start with a question like, "my code doesn't
work", you try to narrow it down to the specific problem for posting.
Sometimes you narrow it to far and the context is lost.

Eric


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Ian Bartholomew-5
In reply to this post by Eric Winger-4
Eric,

> Is this a bug or a result of my misuse of FLOAT?

Neither, it's the inherent imprecision of floating point numbers that cannot
be exactly represented in a binary form. It happens in all languages and
cannot be avoided without changing to another form of representation.  If
you were to try it again with a number that can be expressed exactly in
binary, 1.5 for example, then everything should work as expected.

Dolphin Floats are held in 64 bit values so any inaccuracy is
correspondingly small (around the 15th DP IIRC). When displaying Floats
Dolphin takes account of this and hides (rounds out)  or ignores the extra
digits (see the Float class variables DefaultSigFigs and
SignificantDifference)

FLOATs on the other hand are stored as 32 bit values so the inaccuracy is
larger (each bit means more). Dolphin still displays it as a Float,
expecting the imprecision to be the same as for a 64 bit value, and
therefore doesn't hide the extra digits (the 8th DP in your example)

The fact that the imprecision is still present in Dolphin Floats can be seen
by accumulation over a number of  iterations

f := 1.2.
t := 0.0.
10000000 timesRepeat: [t := t + f].
t inspect
-> answers 11999999.9986769

as well as things like

0.2 + 0.2 + 0.2 = 0.6
-> answers false

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Blair McGlashan
In reply to this post by Eric Winger-4
Eric

You wrote in message news:[hidden email]...

> Hi all!
>
> Why does evaluating the following:
>
> FLOAT new value: 1.2
>
> result in:
>
> a FLOAT(1.20000004768372)
>
>
> Is this a bug or a result of my misuse of FLOAT?

It is neither, it is a precision error. FLOAT is an ExternalStructure for
buffering 32-bit FP numbers. The internal Float class if 64-bit. Historical
note: Some Smalltalk's have 32 and 64-bit FP representations in the Number
hierarchy, reasoning that 32-bit FP isn't really used much these days we
decided, as they have in Squeak, to support only 64-bit double-precision.
When you access the value of a FLOAT the VM primitive responsible promotes
the single-precision number to double-precision, and the result is
inaccurate.

> If its a misuse, how would one setup a float in external memory?

First check that you don't need a DOUBLE. Assuming that the API you are
using really is spec'd in terms of single-precision FLOATs, then you are
doing it right. Of course it is only necessary to use FLOAT/DOUBLE when the
FP numbers in question are [output] parameters, or are embedded in
structs/arrays. For normal call out purposes you can use the float/double
argument type and let the VM marshall the Float object appropriately.

Regards

Blair


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Ian Bartholomew-5
In reply to this post by Eric Winger-5
Eric,

> No, I'm testing some COM code and when I create an external structure
> float object (i.e. instance of class FLOAT), I discovered a bug in my
> code in that what was being created and sent out through an interface
> (1.2) was coming back as 1.20000004768372). Thus my test would break.

Hmmm, I was going to suggest that you round the values first -

((FLOAT new value: 1.2) asFloat roundTo: 0.001) = (1.2 roundTo: 0.001)

which is one of the ways of comparing floats. To use the example I posted
before

((0.2 + 0.2 + 0.2) roundTo: 0.001) = 0.6

now answers true. However I did a bit more testing using ..

1 to: 100 do: [:top |
    top to: 100 do: [:bottom |
        v := top asFloat / bottom asFloat.
        ((FLOAT new value: v) asFloat roundTo: 0.001) = (v roundTo: 0.001)
            ifFalse: [Transcript print: v; cr]]]

and it still fails with a small number of values. I'm not sure why, although
I think it's something to do will increasing the precision loss with the
extra conversions for Float>>FLOAT>>Float. Using #truncateTo: instead of
#roundTo: makes it even worse?

I'll have another think later but, hopefully, someone else will come up with
a better solution.

Regards
    Ian


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Bill Schwab-2
In reply to this post by Blair McGlashan
Hi Blair,

> It is neither, it is a precision error. FLOAT is an ExternalStructure for
> buffering 32-bit FP numbers. The internal Float class if 64-bit.
Historical
> note: Some Smalltalk's have 32 and 64-bit FP representations in the Number
> hierarchy, reasoning that 32-bit FP isn't really used much these days we
> decided, as they have in Squeak, to support only 64-bit double-precision.
> When you access the value of a FLOAT the VM primitive responsible promotes
> the single-precision number to double-precision, and the result is
> inaccurate.

Two reasons that come to mind for using single vs. double precision are: (1)
minimizing storage space when dealing with huge arrays; (2) experimenting
with roundoff error.  Especially in the case of BIG data sets, one would
probably write something in C/C++ (FORTRAN is too scary to think about<g>)
and package it in a DLL.  The only trouble spot that I can think of would be
having the VM do a JIT conversion of an intentionally single precision
number to double; one might want to see the single precision results in
their unaltered state.  Of course, the "visualization" could be done in C
too, or by bypassing Float and directly parsing the bytes of  the FLOAT
(details left as an excise for the reader<g>).  Also, I've frequently had my
doubts as to whether it's possible to control precision in C; FORTRAN at
least appears to offer far better control over it, because that's what it
was designed to do.

With respect to controlling precision to watch results, I'm undecided on
whether that's a holdover from debates about how to represent floats (how to
split the bits between precision and range), or a really great idea that's
getting obscured by FPUs, cheap memory, and really cool adaptive step size
algorithms.  Any takers?

Ironically, the only recent problem I've had with conversion between
float/double was in that auto-generated database front end =:0   I ended up
selecting single precision floats in the database itself, and the VM did the
same thing you described.  Once I realized that it was conversion related
(wasn't sure exactly where it was happening though), I changed the numeric
fields to double in the database.  The existing records still contained
"noise" (we've since fixed them), and new numbers are stored/retrieved as
entered.

Have a good one,

Bill

--
Wilhelm K. Schwab, Ph.D.
[hidden email]


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Blair McGlashan
In reply to this post by Ian Bartholomew-5
Ian, Eric

"Ian Bartholomew" <[hidden email]> wrote in message
news:2a6I7.4852$h4.336192@stones...

> Eric,
>
> > No, I'm testing some COM code and when I create an external structure
> > float object (i.e. instance of class FLOAT), I discovered a bug in my
> > code in that what was being created and sent out through an interface
> > (1.2) was coming back as 1.20000004768372). Thus my test would break.
>
> Hmmm, I was going to suggest that you round the values first -
>
> ((FLOAT new value: 1.2) asFloat roundTo: 0.001) = (1.2 roundTo: 0.001)
> ...

We have this handy method in our TestCase hierarchy:
compare: x to: y epsilon: epsilon
    "Compare that the two <Float> values are equivalent to within the
specified <Float> difference."

    | max |
    ^(max := x abs max: y abs) <= epsilon
        or: [(x - y) abs < (epsilon * max)]

It is in fact a parameterized version of the Float>>equals: method. A
suitable epsilon for single-precision floats would be about 1.0e-6 (though
in fact your test seems to run fine down to 1e-7, failing at 1e-8).

Regards

Blair
------------------------------------------------------------
TestCase subclass: #FLOATTest
 instanceVariableNames: ''
 classVariableNames: ''
 poolDictionaries: ''
 classInstanceVariableNames: ''!
FLOATTest comment: 'SUnitBrowser openOnTestCase: self'!
!FLOATTest methodsFor!

compare: x to: y epsilon: epsilon
 "Compare that the two <Float> values are equivalent to within the specified
<Float> difference."

 | max |
 ^(max := x abs max: y abs) <= epsilon or: [(x - y) abs < (epsilon * max)]!

testFLOATConversions
 1 to: 100
  do:
   [:top |
   top to: 100
    do:
     [:bottom |
     | v |
     v := top asFloat / bottom asFloat.
     self assert: (self
        compare: (FLOAT new value: v) asFloat
        to: v
        epsilon: 1.0e-006)]]! !


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Eric Winger-4
In reply to this post by Blair McGlashan
Blair McGlashan wrote:
>
.....

>
>>If its a misuse, how would one setup a float in external memory?
>>
>
> First check that you don't need a DOUBLE. Assuming that the API you are
> using really is spec'd in terms of single-precision FLOATs, then you are
> doing it right. Of course it is only necessary to use FLOAT/DOUBLE when the
> FP numbers in question are [output] parameters, or are embedded in
> structs/arrays. For normal call out purposes you can use the float/double
> argument type and let the VM marshall the Float object appropriately.
>
>

The spec in question is an [out] spec & is indeed a float. To get by my
TestCase test bug, I just rounded the comparison values to 0.1
precision. I'll keep an eye on it when when I move to actually using the
code between processes.

I added an instance creation method fromFloat: so that it would be more
symmetric with ExternalInteger creations. DOUBLE should probably have
one too. Not sure why this was left out.

Thanks all,

Eric


Reply | Threaded
Open this post in threaded view
|

Re: FLOAT creation incorrect?

Jan Theodore Galkowski-2
In reply to this post by Blair McGlashan
Blair McGlashan wrote:
>
> Ian, Eric
>
> "Ian Bartholomew" <[hidden email]> wrote in message
> news:2a6I7.4852$h4.336192@stones...
> > Eric,
> >

Blair,

YES!  This was the direction I was heading with my general query
about Eric's situation.  Indeed, the upshot of "what every...should
know about flonums" is that traditional equality is not defined for
floating point numbers and an epsilon-based test is needed.

Indeed, one will find this presentation in almost any book on
numerical analysis and it is a cornerstone of numerical work
since LINPACK first came out.

The definitive guide to numerical computing with Smalltalk is the
great and recent OBJECT-ORIENTED IMPLEMENTATION OF NUMERICAL
METHODS by Didier Besset which belongs, I believe, in every
Smalltalkers library.  He's got a section 1.5 dealing with
comparison of flonums which discusses this.  Besset is available
via bookpool.com at

  http://www.bookpool.com/.x/oh64r7hlri/ss/1?qs=Besset&Go.x=7&Go.y=7

[snip]

>
> We have this handy method in our TestCase hierarchy:
> compare: x to: y epsilon: epsilon
>     "Compare that the two <Float> values are equivalent to within the
> specified <Float> difference."
>
>     | max |
>     ^(max := x abs max: y abs) <= epsilon
>         or: [(x - y) abs < (epsilon * max)]
>
> It is in fact a parameterized version of the Float>>equals: method. A
> suitable epsilon for single-precision floats would be about 1.0e-6 (though
> in fact your test seems to run fine down to 1e-7, failing at 1e-8).


[snip]

--
---------------------------------------------------------------------
 Jan Theodore Galkowski                    [hidden email]
 The Smalltalk Idiom                       [hidden email]
*********************************************************************
             "Smalltalk?  Yes, it's really that slick."
---------------------------------------------------------------------
Want to know more?  Check out
           http://www.dnsmith.com/SmallFAQ/
           http://www.object-arts.com/DolphinWhitePaper.htm
           http://st-www.cs.uiuc.edu/users/johnson/smalltalk/
*********************************************************************