<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christian Lauridsen</title>
    <description>The latest articles on DEV Community by Christian Lauridsen (@webwizard).</description>
    <link>https://dev.to/webwizard</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4204290%2F0f6f4414-8d0b-4b20-9b3e-cded57d748be.jpg</url>
      <title>DEV Community: Christian Lauridsen</title>
      <link>https://dev.to/webwizard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/webwizard"/>
    <language>en</language>
    <item>
      <title>Show Live Crypto Prices in a Laravel App Without Float Bugs</title>
      <dc:creator>Christian Lauridsen</dc:creator>
      <pubDate>Wed, 28 Oct 2026 18:12:09 +0000</pubDate>
      <link>https://dev.to/webwizard/show-live-crypto-prices-in-a-laravel-app-without-float-bugs-18f3</link>
      <guid>https://dev.to/webwizard/show-live-crypto-prices-in-a-laravel-app-without-float-bugs-18f3</guid>
      <description>&lt;p&gt;A price widget in a Laravel app looks like a ten minute job: call an API, cache the result, print the number. The two places it goes wrong are the cache (you hammer the API on every page view) and the math (you cast a price to &lt;code&gt;float&lt;/code&gt; and lose digits). This post wires up both properly, using the official PHP SDK for the Bitculator Data API.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install the SDK
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require bitculator/sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SDK needs PHP 8.1+, &lt;code&gt;ext-curl&lt;/code&gt; and &lt;code&gt;ext-json&lt;/code&gt;, and has no other dependencies. Get an API key from &lt;a href="https://bitculator.com/user/developer/api" rel="noopener noreferrer"&gt;your Bitculator developer page&lt;/a&gt; and put it in &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BITCULATOR_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-key-here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the key server-side. It is a Bearer token and does not belong in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Register the client once
&lt;/h2&gt;

&lt;p&gt;Add the key to &lt;code&gt;config/services.php&lt;/code&gt;, so &lt;code&gt;config:cache&lt;/code&gt; works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="s1"&gt;'bitculator'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'BITCULATOR_API_KEY'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then bind one client in &lt;code&gt;AppServiceProvider::register()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bitculator\Bitculator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;Bitculator&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Bitculator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'services.bitculator.key'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anything in your app can now type-hint &lt;code&gt;Bitculator&lt;/code&gt; and get the same configured instance. The constructor also takes &lt;code&gt;timeout&lt;/code&gt; and &lt;code&gt;maxRetries&lt;/code&gt; (the SDK retries 429, 5xx and network errors twice by default).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Put the API behind a small service and a cache
&lt;/h2&gt;

&lt;p&gt;Do not call the SDK from controllers or Blade. One small class keeps the API call, the cache and the error handling in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Services&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bitculator\Bitculator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Bitculator\Exceptions\RateLimitException&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CoinPrices&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;__construct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;Bitculator&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$slug&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;"bitculator:price:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$slug&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;coins&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$slug&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RateLimitException&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Prices are busy, try again shortly.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="s1"&gt;'Retry-After'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;retryAfter&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here. &lt;code&gt;Cache::remember&lt;/code&gt; means a page that gets 10,000 views a minute makes one API call a minute per coin, not 10,000. And the method returns a &lt;code&gt;string&lt;/code&gt;, which is deliberate.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Prices are strings, keep them strings
&lt;/h2&gt;

&lt;p&gt;The API returns prices as decimal strings, for example &lt;code&gt;"63520.780763913"&lt;/code&gt;. That is on purpose: a JSON number is parsed into a &lt;code&gt;float&lt;/code&gt; by most clients, and a float cannot hold every decimal digit of a price exactly. If you cast it, you silently change the value.&lt;/p&gt;

&lt;p&gt;So do the math with BCMath (&lt;code&gt;ext-bcmath&lt;/code&gt;), which works on strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CoinPrices&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bitcoin'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "63520.780763913"&lt;/span&gt;
&lt;span class="nv"&gt;$holding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0.25'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bcmul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$holding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bcmul&lt;/code&gt; takes the scale as its third argument, so you decide how many decimals to keep. Only format the number for display at the last moment, in the Blade view or a resource, and never feed the formatted string back into more math.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. List the top coins
&lt;/h2&gt;

&lt;p&gt;The SDK returns a &lt;code&gt;Page&lt;/code&gt;. Iterating it walks every page for you, so for a top-50 table you ask for one page and read &lt;code&gt;data&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;coins&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'per_page'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sort'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'-marketcap'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$page&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$coin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$coin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'symbol'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$coin&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'price'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache this the same way as the single price, with the key including the &lt;code&gt;per_page&lt;/code&gt; and &lt;code&gt;sort&lt;/code&gt; values.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Know your quota
&lt;/h2&gt;

&lt;p&gt;After every call the client exposes the rate-limit headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$quota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;quota&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// limit, remaining, reset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log &lt;code&gt;remaining&lt;/code&gt; somewhere you will see it, so a traffic spike shows up on a graph before it shows up as 429s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The pattern is: one singleton client, one service, a short cache, strings in and BCMath for math. The same API also has official SDKs for TypeScript, Python, Go, Java, .NET, Rust and C++, so the pattern carries over if part of your stack is not PHP. Full endpoint list is in the &lt;a href="https://bitculator.com/en/documentation/api/v1" rel="noopener noreferrer"&gt;API documentation&lt;/a&gt;, and the SDK source is on &lt;a href="https://github.com/Bitculator/bitculator-php-sdk" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If you want the reasoning behind decimal strings in more depth, I wrote about it in &lt;a href="https://dev.to/webwizard/why-a-crypto-price-api-should-send-decimal-strings-not-numbers-6df"&gt;Why a crypto price API should send decimal strings, not numbers&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>api</category>
      <category>cryptocurrency</category>
    </item>
    <item>
      <title>Why a crypto price API should send decimal strings, not numbers</title>
      <dc:creator>Christian Lauridsen</dc:creator>
      <pubDate>Sat, 24 Oct 2026 09:26:11 +0000</pubDate>
      <link>https://dev.to/webwizard/why-a-crypto-price-api-should-send-decimal-strings-not-numbers-6df</link>
      <guid>https://dev.to/webwizard/why-a-crypto-price-api-should-send-decimal-strings-not-numbers-6df</guid>
      <description>&lt;p&gt;If you consume a market-data API, you will add, multiply and round prices. A common cause of wrong totals in that code is that the price was already a binary float before the developer's own code ran.&lt;/p&gt;

&lt;p&gt;This post shows where that happens, with examples I ran, and the fix: an API that sends prices as decimal strings, and a client that parses them on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-line version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;
&lt;span class="mf"&gt;0.30000000000000004&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Binary floating point cannot store most decimal fractions exactly. For a game that is invisible. For money it is the reason accounting software uses decimal types.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three places it bites in a price feed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Arithmetic on a price
&lt;/h3&gt;

&lt;p&gt;Take a plausible Bitcoin price and multiply it by 3, as a floating-point library would, and as a decimal library would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;63520.780763913&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="mf"&gt;190562.34229173898&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;63520.780763913&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;190562.342291739&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The float answer carries digits the input never had. Nothing is wrong enough to crash, which is the problem: the error only appears when you compare totals, reconcile against another system, or store the result and read it back.&lt;/p&gt;

&lt;p&gt;Accumulation makes it worse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="mf"&gt;0.9999999999999999&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
&lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Summing a thousand ticks or fills is the same loop with more room for the error to grow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Integers above 2^53
&lt;/h3&gt;

&lt;p&gt;JavaScript numbers are IEEE 754 doubles. Every integer up to &lt;code&gt;Number.MAX_SAFE_INTEGER&lt;/code&gt;, 9007199254740991, is exact; past that, some integers cannot be represented and get rounded to a neighbour. Token amounts in the smallest unit (wei, for example) go far beyond that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_SAFE_INTEGER&lt;/span&gt;
&lt;span class="mi"&gt;9007199254740991&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1234567890123456789&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;1234567890123456800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last digits are gone, silently. &lt;code&gt;JSON.parse&lt;/code&gt; does the same thing to any number in a response, because JSON has a single &lt;code&gt;number&lt;/code&gt; type and the parser chooses a double:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"supply":1234567890123456789}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;supply&lt;/span&gt;
&lt;span class="mi"&gt;1234567890123456800&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python's &lt;code&gt;json&lt;/code&gt; keeps large integers exact, but it still turns &lt;code&gt;0.1&lt;/code&gt; into a float unless you tell it otherwise.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rounding at display time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.675&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.67499999999999982236431605997495353221893310546875&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.675&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2.67&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2.675&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;quantize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;2.68&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The float &lt;code&gt;2.675&lt;/code&gt; is really 2.67499999999999982..., so it rounds down. The decimal &lt;code&gt;2.675&lt;/code&gt; is exactly what you wrote, so the rounding rule applies to your number. Note that &lt;code&gt;Decimal&lt;/code&gt; rounds half to even by default (&lt;code&gt;Decimal("0.125")&lt;/code&gt; becomes &lt;code&gt;0.12&lt;/code&gt;), so choose the rule you want on purpose; see the checklist.&lt;/p&gt;

&lt;p&gt;A fair caveat: a single ordinary price such as &lt;code&gt;63520.780763913&lt;/code&gt; survives a trip through a float. The damage shows up in arithmetic, large integers and accumulated sums, as above. That is why it goes unnoticed in testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the API is the right place to fix it
&lt;/h2&gt;

&lt;p&gt;An API that sends this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;63520.780763913&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;has already made the choice for you. By default, your JSON parser turns the number into a double, and your code never sees the original digits. (Some parsers can be told otherwise, as the Python example below shows, but you have to know to ask.)&lt;/p&gt;

&lt;p&gt;An API that sends this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"63520.780763913"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;hands the decision to the developer. The string cannot lose precision in transit. It costs one extra step on the client, parsing it into a decimal type, and that step is the point: you now do it deliberately.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://bitculator.com/en/crypto-api" rel="noopener noreferrer"&gt;Bitculator Data API&lt;/a&gt;, which I work on, sends prices, rates and supplies as decimal strings for this reason. I will use it only as the example below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The client side, in four languages
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Python.&lt;/strong&gt; Parse with &lt;code&gt;Decimal&lt;/code&gt;. If you must read numbers from a response that sends floats, &lt;code&gt;json.loads&lt;/code&gt; can do it for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 0.1}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parse_float&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nc"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a string field, &lt;code&gt;Decimal(response["price"])&lt;/code&gt; is enough. The official Python SDK returns prices as strings and its README says to use &lt;code&gt;Decimal&lt;/code&gt; for any arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript / TypeScript.&lt;/strong&gt; There is no built-in decimal type. Keep the value as a string until you need math, then use a decimal library, or &lt;code&gt;BigInt&lt;/code&gt; when the quantity is a whole number of smallest units. Never &lt;code&gt;Number(price)&lt;/code&gt; for something you will sum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Go.&lt;/strong&gt; Decode into a &lt;code&gt;string&lt;/code&gt; field, not a &lt;code&gt;float64&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// raw is the json.RawMessage the SDK returns&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Symbol&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"symbol"`&lt;/span&gt;
    &lt;span class="n"&gt;Price&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"price"`&lt;/span&gt; &lt;span class="c"&gt;// decimal string, never rounded&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then calculate with &lt;code&gt;big.Rat&lt;/code&gt;, which parses and holds a decimal string exactly (&lt;code&gt;SetString("63520.780763913")&lt;/code&gt;), or with a decimal package. Avoid &lt;code&gt;big.Float&lt;/code&gt; for this: it is still binary floating point, just with more bits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Java.&lt;/strong&gt; &lt;code&gt;new BigDecimal(priceString)&lt;/code&gt;, and beware &lt;code&gt;new BigDecimal(someDouble)&lt;/code&gt;, which captures the float's error too.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist for API consumers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Check the API's docs: is a price a number or a string? If it is a number, your parser has already chosen a double.

&lt;ol&gt;
&lt;li&gt;Keep prices as strings or decimal types from the wire to the database. Convert to float only for charts, where a pixel is the resolution.&lt;/li&gt;
&lt;li&gt;Never sum floats for anything that must reconcile. Sum decimals, or integers in the smallest unit.&lt;/li&gt;
&lt;li&gt;Decide your rounding rule explicitly (half up, half even) and apply it with a decimal type, once, at the edge.&lt;/li&gt;
&lt;li&gt;Put one test in your suite with a value that breaks floats, such as 0.1 + 0.2 or an integer above 2^53.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Docs and source code
&lt;/h2&gt;

&lt;p&gt;The API reference is at &lt;a href="https://bitculator.com/en/documentation/api/v1" rel="noopener noreferrer"&gt;https://bitculator.com/en/documentation/api/v1&lt;/a&gt;. The SDKs I mentioned are MIT-licensed and have no dependencies. The Python and Go ones are at &lt;a href="https://github.com/Bitculator/bitculator-python-sdk" rel="noopener noreferrer"&gt;https://github.com/Bitculator/bitculator-python-sdk&lt;/a&gt; and &lt;a href="https://github.com/Bitculator/bitculator-go-sdk" rel="noopener noreferrer"&gt;https://github.com/Bitculator/bitculator-go-sdk&lt;/a&gt;. You do not need an API key to read the code.&lt;/p&gt;




</description>
      <category>api</category>
      <category>python</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
